Compare commits
100 Commits
c0fdbc4d4a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| aeebd8a165 | |||
| 3bb7fa1bae | |||
| 66e7fe4e71 | |||
| 563afe82b0 | |||
| 3cfd561504 | |||
| 652430b42b | |||
| 6e0b016f71 | |||
| 72478f5871 | |||
| 90e4e77248 | |||
| 7366a35af5 | |||
| 9672b857a4 | |||
| ba22af10c7 | |||
| 6e76daa133 | |||
| 260845b2e3 | |||
| 42f7839aeb | |||
| ecf0cae3ff | |||
| 05d1f0f8e5 | |||
| 75c799312a | |||
| 1cc272795b | |||
| 32da5f6762 | |||
| e569e3c856 | |||
| 3fc17a03ed | |||
| a351c289b3 | |||
| 1f200d68ef | |||
| 644f912a04 | |||
| 202c494fc6 | |||
| 52f7b34dfc | |||
| 3d8b1e4399 | |||
| 96ca617806 | |||
| 7bf748ac37 | |||
| f8bfc0c92d | |||
| d846802baa | |||
| d23afa1a0a | |||
| 677b9e355d | |||
| eff51a2903 | |||
| c67ecacf76 | |||
| 1bd068c025 | |||
| 67a8ee5ca4 | |||
| 864c83ac94 | |||
| 82cba01179 | |||
| 7a281e47a4 | |||
| eea8b16446 | |||
| 6daf3bce76 | |||
| 3cee695b8d | |||
| ce1ecc6e15 | |||
| 6da6dea2d0 | |||
| 3e01fac842 | |||
| 27e5bb921e | |||
| 537dcdbc6b | |||
| 6cb35647c7 | |||
| d3ae6f872f | |||
| d0ba79906f | |||
| dc8886aee1 | |||
| e5cecf2dd1 | |||
| a4f59b9574 | |||
| ecde8df67f | |||
| 589b19bb2b | |||
| 70bf45d719 | |||
| b8215d7afe | |||
| aa6a8f0120 | |||
| 8050318001 | |||
| fd9a9a7755 | |||
| e7d20d1619 | |||
| 35131584ea | |||
| cbd7dca04c | |||
| 28ab94f60b | |||
| 62e52349cb | |||
| 2f13195a48 | |||
| 5f8610bfe2 | |||
| 9e7d78ccb2 | |||
| cb9c0c5e0d | |||
| 1278f9fe42 | |||
| 8665b25c16 | |||
| 56e2f38da7 | |||
| 7597179651 | |||
| 27fc455b9f | |||
| 69fa7be9e8 | |||
| 888a9f7afa | |||
| 504779ce21 | |||
| 54f567457a | |||
| 91a9c6ad24 | |||
| 11047b6349 | |||
| 042c10ace4 | |||
| 51101cbd57 | |||
| f31e668dee | |||
| 61c5185d81 | |||
| 2c46d28956 | |||
| d7a1829592 | |||
| 80307afdbc | |||
| ed8e75295e | |||
| 6d2b07b1b5 | |||
| 75e1fab8d4 | |||
| 61963e72f5 | |||
| c864c670b7 | |||
| a256c14932 | |||
| ed70f6e3c2 | |||
| 94775414bd | |||
| 36d4fc205e | |||
| 428f819f9c | |||
| bd7278569e |
@@ -0,0 +1,23 @@
|
||||
# Validates that all .cook files in this repository are valid Cooklang recipes.
|
||||
# Uses cookcli (https://hub.docker.com/r/inigochoa/cookcli) via Docker.
|
||||
name: Validate Cooklang
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
branches: [main, master]
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Validate all .cook files
|
||||
run: |
|
||||
docker run --rm \
|
||||
--entrypoint cook \
|
||||
-v "$(pwd):/recipes:ro" \
|
||||
docker.io/inigochoa/cookcli \
|
||||
doctor validate -b /recipes --strict
|
||||
@@ -4,18 +4,14 @@ title: Alain Ducasse's Gougères
|
||||
|
||||
servings: 28
|
||||
|
||||
category: Bread
|
||||
|
||||
category: appetizer
|
||||
source: https://www.foodandwine.com/recipes/alain-ducasses-gougeres
|
||||
|
||||
description: "Alain Ducasse's recipe for gougères. These airy French cheese puffs, flavored with Gruyère cheese and a hint of nutmeg, make phenomenal hors d'oeuvres."
|
||||
|
||||
tags:
|
||||
|
||||
- bread
|
||||
|
||||
- vegetarian
|
||||
- french
|
||||
|
||||
- cheese
|
||||
image: https://roux.roo.lol/recipe-images/alain-ducase-bread.webp
|
||||
---
|
||||
|
||||
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
title: Apple Cider Vinegar Dressing
|
||||
description: This homemade apple cider vinaigrette is SO much better than any store bought dressing! Made with 7 simple ingredients, it's tangy, healthy, and easy to make.
|
||||
servings: 6
|
||||
category: salad
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- gluten-free
|
||||
source: https://www.loveandlemons.com/apple-cider-vinegar-dressing/
|
||||
image: https://kitchen.roo.lol/data/images/14663763-8d52-4e31-a4e8-36a7f3c6c229.webp
|
||||
image: https://roux.roo.lol/recipe-images/apple-cider-vinegar-dressing.webp
|
||||
thumbnail: https://kitchen.roo.lol/data/images/thumbnails/14663763-8d52-4e31-a4e8-36a7f3c6c229.webp
|
||||
date_created: 2024-11-03
|
||||
date_modified: 2024-11-03
|
||||
|
||||
@@ -8,18 +8,13 @@ prep_time: 25 minutes
|
||||
|
||||
time: 25 minutes
|
||||
|
||||
category: main course
|
||||
category: dinner
|
||||
|
||||
tags:
|
||||
|
||||
- meat
|
||||
- chicken
|
||||
|
||||
- garnish-and-gather
|
||||
|
||||
- orzo
|
||||
|
||||
- pesto
|
||||
|
||||
- pasta
|
||||
image: https://roux.roo.lol/recipe-images/arugula-pesto-chicken-with-pecans-roasted-butternut.webp
|
||||
---
|
||||
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
---
|
||||
title: Arugula Pesto Wheat Berries Recipe
|
||||
servings: 1
|
||||
category: uncategorized
|
||||
category: lunch
|
||||
source: https://www.101cookbooks.com/archives/arugula-pesto-wheat-berries-recipe.html
|
||||
description: "A peppery arugula pesto tossed with a bowl of plump, chewy wheat berries."
|
||||
tags:
|
||||
- arugula
|
||||
- pesto
|
||||
- wheat berries
|
||||
- vegetarian
|
||||
- grain-bowl
|
||||
image: https://roux.roo.lol/recipe-images/arugula-pesto-wheat-berries-recipe.webp
|
||||
---
|
||||
|
||||
Place the @cooked wheat berries{3%cups} in a large mixing bowl and set them aside so they can come up to room temperature if you've had them in the refrigerator.
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
title: Asparagus Salad
|
||||
prep_time: 20 minutes
|
||||
cook_time: 5 minutes
|
||||
category: Salad
|
||||
category: salad
|
||||
tags:
|
||||
- vegetarian
|
||||
- gluten-free
|
||||
- spring
|
||||
- salad
|
||||
description: A fresh spring asparagus salad with peas, radishes, feta, pine nuts, and herbs, tossed with lemon and olive oil and finished with creamy avocado dressing.
|
||||
source: Love and Lemons
|
||||
image: https://roux.roo.lol/recipe-images/asparagus-salad.webp
|
||||
---
|
||||
|
||||
Bring a large #pot of well-salted @water{} to a boil and prepare a #bowl of @ice water{}.
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
---
|
||||
title: Authentic Cacio e Pepe Recipe
|
||||
servings: 2
|
||||
category: uncategorized
|
||||
category: dinner
|
||||
description: "A cacio e pepe is so simple, the whole recipe is 3 ingredients: pecorino romano, black pepper and pasta."
|
||||
source: https://pinabresciani.com/authentic-cacio-e-pepe-recipe/
|
||||
tags:
|
||||
- pasta
|
||||
- vegetarian
|
||||
- italian
|
||||
- quick
|
||||
- pasta
|
||||
image: https://roux.roo.lol/recipe-images/authentic-cacio-e-pepe-recipe.webp
|
||||
---
|
||||
|
||||
In a #pot, bring @water{} to a boil. Lightly salt the water when it comes to a boil.
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
title: BUTTERNUT & TURKEY CHILI with sweet peppers, spiced pepitas & queso fresco
|
||||
servings: 1
|
||||
prep_time: 20 minutes
|
||||
time: 1 hour
|
||||
category: mains
|
||||
description: "A hearty chili made with ground turkey, butternut squash, and sweet peppers, topped with spiced pepitas and queso fresco."
|
||||
tags:
|
||||
- butternut squash
|
||||
- chili
|
||||
- turkey
|
||||
---
|
||||
|
||||
Heat ~{1%Tbsp} @olive oil{} in a #pot over medium heat.
|
||||
|
||||
Add @pepitas{1/4%cups}, pepita spice (¼%tsp smoked paprika, ⅛%tsp cayenne, ⅛%tsp cinnamon) and a pinch of @salt{}. Toast, stirring frequently, until fragrant and lightly browned, about ~{3-5%minutes}, then remove and set aside.
|
||||
|
||||
Chop @sweet pepper{3/4%cup} and @cilantro{}; drain and rinse @beans{}; cut @lime{} into wedges.
|
||||
|
||||
In the same #pot, heat ~{2%Tbsp} @olive oil over medium-high.
|
||||
|
||||
Add @ground turkey{8%oz} and ~{1/4%tsp} each @salt{} & @pepper{}, cook ~{5-8%minutes} until browned, breaking up; remove and set aside.
|
||||
|
||||
Add @butternut squash{1.5%cups} and @onion{2%tbsp}(chopped) to the pot and cook until beginning to soften, ~{5-7%minutes}.
|
||||
|
||||
Add Chili Spice (1%Tbsp chili powder, 2%tsp cumin, 1%tsp smoked paprika, 1/2%tsp dried oregano) and cook ~{1%minute} until fragrant.
|
||||
|
||||
Stir in @tomatoes{}, @broth{}, @water{1/2%cup}, cooked turkey, @beans{} and ~{1/2%tsp} @salt{}. Bring to a boil, reduce heat to low and simmer until butternut is tender, about ~{20%minutes}.
|
||||
|
||||
Crumble @queso fresco{}.
|
||||
|
||||
Adjust seasoning to taste.
|
||||
|
||||
Serve chili in bowls topped with spiced @pepitas{}, sour cream (optional), queso fresco, cilantro & lime wedges.
|
||||
@@ -3,12 +3,13 @@ title: Baked Chile Relleno with Black Beans
|
||||
servings: 2
|
||||
prep_time: 25 minutes
|
||||
time: 25 minutes
|
||||
category: main course
|
||||
category: dinner
|
||||
description: "Roasted poblano peppers stuffed with a black bean and Monterey Jack filling, baked with ranchero sauce."
|
||||
tags:
|
||||
- mexican
|
||||
- peppers
|
||||
- vegetarian
|
||||
- mexican
|
||||
- beans
|
||||
image: https://roux.roo.lol/recipe-images/baked-chile-relleno-with-black-beans.webp
|
||||
---
|
||||
|
||||
Preheat the #oven to high broil with a rack near the broiler.
|
||||
|
||||
+5
-4
@@ -2,14 +2,15 @@
|
||||
title: Bang Bang Tofu
|
||||
servings: 2
|
||||
prep_time: 20 minutes
|
||||
category: main course
|
||||
category: dinner
|
||||
cuisine: Asian Fusion
|
||||
description: Crispy fried tofu with spicy bang bang sauce served over scallion rice with snow peas
|
||||
tags:
|
||||
- asian fusion
|
||||
- rice
|
||||
- tofu
|
||||
- vegetarian
|
||||
- vegan-option
|
||||
- asian
|
||||
- tofu
|
||||
image: https://roux.roo.lol/recipe-images/bang-bang-tofu.webp
|
||||
---
|
||||
|
||||
Drain @tofu{1%lb}, pat dry, and place on a plate lined with several paper towels or a clean dish towel to dry for ~{15%minutes}.
|
||||
|
||||
@@ -3,12 +3,12 @@ title: Barbecue Chicken with Crispy Pan-Fried Okra & Cheddar Biscuits
|
||||
servings: 2
|
||||
prep_time: 20 minutes
|
||||
time: 20 minutes
|
||||
category: main course
|
||||
category: dinner
|
||||
tags:
|
||||
- bbq
|
||||
- meat
|
||||
- chicken
|
||||
- biscuits
|
||||
- okra
|
||||
- southern
|
||||
image: https://roux.roo.lol/recipe-images/barbecue-chicken-with-crispy-pan-fried-okra-cheddar-biscuits.webp
|
||||
---
|
||||
|
||||
Preheat the #oven to 400°F and lightly grease a #baking sheet.
|
||||
|
||||
@@ -6,12 +6,11 @@ category: salad
|
||||
source: https://www.loveandlemons.com/black-bean-and-corn-salad/
|
||||
description: "This black bean and corn salad is a delicious summer side dish or healthy lunch! It keeps well for up to 3 days in the fridge, but if you're making it in advance, wait to add the avocado until right before you eat. Otherwise, it will start to brown."
|
||||
tags:
|
||||
- black beans
|
||||
- corn
|
||||
- salad
|
||||
- avocado
|
||||
- summer
|
||||
- healthy
|
||||
- vegetarian
|
||||
- vegan
|
||||
- gluten-free
|
||||
- mexican
|
||||
image: https://roux.roo.lol/recipe-images/black-bean-and-corn-salad.webp
|
||||
---
|
||||
|
||||
In a large #mixing bowl{}, whisk together the @lime juice{1/4%cup}, @avocado oil{3%tablespoons}, @garlic{1%clove}, grated, @cumin{1/2%teaspoon}, @sea salt{3/4%teaspoon}, and @black pepper{}.
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
---
|
||||
title: Black Pepper Tofu (Ottolenghi)
|
||||
description:
|
||||
servings: 1
|
||||
category: dinner
|
||||
source: Unknown
|
||||
image: https://roux.roo.lol/recipe-images/black-pepper-tofu-ottolenghi.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- asian
|
||||
- tofu
|
||||
- vegan-option
|
||||
---
|
||||
|
||||
>> Servings: 4
|
||||
Pour enough @vegetable oil{} into a large #frying pan or wok{} to come 1/4 inch and turn on heat.
|
||||
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
---
|
||||
title: Black Pepper Tofu with Bok Choy
|
||||
description:
|
||||
servings: 1
|
||||
category: dinner
|
||||
source: Unknown
|
||||
image: https://roux.roo.lol/recipe-images/black-pepper-tofu-with-bok-choy.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- asian
|
||||
- tofu
|
||||
- vegan-option
|
||||
---
|
||||
|
||||
>> Servings: 2
|
||||
>> Prep time: 30 minutes
|
||||
>> Total time: 30 minutes
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
---
|
||||
title: Blackened Shrimp & Gouda Grits
|
||||
servings: 2
|
||||
category: main course
|
||||
category: dinner
|
||||
cuisine: Southern
|
||||
tags: caesar salad,garnish-and-gather,seafood,shrimp and grits,southern
|
||||
tags:
|
||||
- seafood
|
||||
- shrimp
|
||||
- southern
|
||||
source: Unknown
|
||||
description: Southern-style shrimp and grits with smoked gouda, served with a charred scallion kale caesar salad
|
||||
prep_time: 25 minutes
|
||||
image: https://roux.roo.lol/recipe-images/blackened-shrimp-and-gouda-grits.webp
|
||||
---
|
||||
|
||||
In a #pot{}, bring 3 cups @water{} & @salt{1/2 teaspoon} to a boil.
|
||||
|
||||
+6
-2
@@ -1,10 +1,14 @@
|
||||
---
|
||||
title: Tofu Makhani (Indian Butter Tofu)
|
||||
servings: 6
|
||||
category: uncategorized
|
||||
category: dinner
|
||||
source: https://cooking.nytimes.com/recipes/1020094-tofu-makhani-indian-butter-tofu
|
||||
description: This recipe is a vegetarian riff on butter chicken, or murgh makhani, a classic Indian dish in which chicken simmers in a rich, dairy-laden tomato sauce that's seasoned with heady spices like turmeric, cumin, garam masala and cardamom. This weeknight-friendly version has a toned-down ingredient list, and the tofu doesn't require hours of marinating, which means you can get everything on the table in under an hour if you start the rice as the tomatoes are cooking down. Heavy cream gives this dish a rich finish, but full-fat coconut milk makes a fine substitute. Whatever you do, don't substitute anything for butter or ghee (sorry, vegans) — it's signature ingredient.
|
||||
image: https://kitchen.roo.lol/data/images/99596aa7-0678-40ce-8020-f2ae9308be6a.webp
|
||||
image: https://roux.roo.lol/recipe-images/butter-tofu.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- indian
|
||||
- tofu
|
||||
---
|
||||
|
||||
In a large #bowl{}, whisk @fresh lemon juice{2 tablespoons}, @ground cumin{1 tablespoon} and 1 heaping tablespoon @ground turmeric{} with 1 tablespoon @water{} to make a thin paste. Drain and pat @tofu{2 (14-ounce) packages, firm} dry then cut it into 1-inch cubes. Add @tofu{} to the @turmeric{} marinade, gently stirring to coat. Set aside at room temperature while you prepare the remaining ingredients, or cover and refrigerate for up to 1 day.
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
---
|
||||
title: Butternut and Turkey Chili
|
||||
servings: 1
|
||||
prep_time: 20 minutes
|
||||
time: 1 hour
|
||||
category: mains
|
||||
description: "A hearty chili made with ground turkey, butternut squash, and sweet peppers, topped with spiced pepitas and queso fresco."
|
||||
tags:
|
||||
- butternut squash
|
||||
- chili
|
||||
- turkey
|
||||
---
|
||||
|
||||
Heat ~{1%Tbsp} @olive oil{} in a #pot over medium heat.
|
||||
|
||||
Add @pepitas{1/4%cups}, pepita spice (¼%tsp smoked paprika, ⅛%tsp cayenne, ⅛%tsp cinnamon) and a pinch of @salt{}. Toast, stirring frequently, until fragrant and lightly browned, about ~{3-5%minutes}, then remove and set aside.
|
||||
|
||||
Chop @sweet pepper{3/4%cup} and @cilantro{}; drain and rinse @beans{}; cut @lime{} into wedges.
|
||||
|
||||
In the same #pot, heat ~{2%Tbsp} @olive oil over medium-high.
|
||||
|
||||
Add @ground turkey{8%oz} and ~{1/4%tsp} each @salt{} & @pepper{}, cook ~{5-8%minutes} until browned, breaking up; remove and set aside.
|
||||
|
||||
Add @butternut squash{1.5%cups} and @onion{2%tbsp}(chopped) to the pot and cook until beginning to soften, ~{5-7%minutes}.
|
||||
|
||||
Add Chili Spice (1%Tbsp chili powder, 2%tsp cumin, 1%tsp smoked paprika, 1/2%tsp dried oregano) and cook ~{1%minute} until fragrant.
|
||||
|
||||
Stir in @tomatoes{}, @broth{}, @water{1/2%cup}, cooked turkey, @beans{} and ~{1/2%tsp} @salt{}. Bring to a boil, reduce heat to low and simmer until butternut is tender, about ~{20%minutes}.
|
||||
|
||||
Crumble @queso fresco{}.
|
||||
|
||||
Adjust seasoning to taste.
|
||||
|
||||
Serve chili in bowls topped with spiced @pepitas{}, sour cream (optional), queso fresco, cilantro & lime wedges.
|
||||
+6
-1
@@ -1,8 +1,13 @@
|
||||
---
|
||||
title: Cabbage Salad
|
||||
servings: 6
|
||||
category: Salad
|
||||
category: salad
|
||||
source: https://www.loveandlemons.com/cabbage-salad/
|
||||
image: https://roux.roo.lol/recipe-images/cabbage-salad.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- gluten-free
|
||||
---
|
||||
|
||||
In a large bowl, toss together @green cabbage{4%cup}, @red cabbage{4%cup}, @red bell pepper{1%medium}, @carrots{1%cup}(julienned), @cilantro{1/2%cup}(chopped).
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
---
|
||||
title: Cajun Shrimp and Sausage Vegetable Foil Packets
|
||||
servings: 4
|
||||
category: uncategorized
|
||||
category: dinner
|
||||
source: https://therecipecritic.com/cajun-shrimp-and-sausage-vegetable-foil-packets/
|
||||
description: Cajun Shrimp and Sausage Foil Packets are loaded with shrimp, sausage, summer vegetables and tossed in cajun spices. These are quick and easy and packed with big flavor!
|
||||
image: https://kitchen.roo.lol/data/images/9f6c7d9f-c6d7-4ea3-bad7-dcffe1187cba.webp
|
||||
image: https://roux.roo.lol/recipe-images/cajun-shrimp-and-sausage-vegetable-foil-packets.webp
|
||||
tags:
|
||||
- seafood
|
||||
- shrimp
|
||||
- cajun
|
||||
- sausage
|
||||
---
|
||||
|
||||
In a large #bowl{} add @shrimp{1 pound, large, peeled and deveined}, @sausage{14 ounce, pork or chicken, sliced}, @zucchini{2 medium sized, sliced}, @yellow squash{2 medium sized, sliced}, @asparagus{½ bunch, sliced into thirds}, @red bell pepper{2, chopped into chunks} and @salt{} and @pepper{}. Add @olive oil{3 Tablespoons} and @cajun seasoning{3 Tablespoons} and toss until coated.
|
||||
|
||||
@@ -3,11 +3,15 @@ title: Cajun Spice Mix (for pecans, oysters, etc)
|
||||
description: A Leonard-family recipe for spice mix for seasoning Cajun Pecans or Oysters
|
||||
servings: 9
|
||||
source: Mealie
|
||||
image: https://kitchen.roo.lol/data/images/c0540c17-29a5-4fe3-b038-1b421037c6e8.webp
|
||||
image: https://roux.roo.lol/recipe-images/cajun-spice-mix-for-pecans-oysters-etc.webp
|
||||
thumbnail: https://kitchen.roo.lol/data/images/thumbnails/c0540c17-29a5-4fe3-b038-1b421037c6e8.webp
|
||||
date_created: 2024-11-03
|
||||
date_modified: 2024-11-03
|
||||
date_published: 2024-11-03
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- spice-blend
|
||||
---
|
||||
|
||||
= Spice Mix =
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
---
|
||||
title: Caramel Cake
|
||||
description:
|
||||
servings: 1
|
||||
category: lunch
|
||||
source: Unknown
|
||||
image: https://roux.roo.lol/recipe-images/caramel-cake.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- dessert
|
||||
---
|
||||
|
||||
>> Servings: 10
|
||||
|
||||
Combine @sour cream{8 ounces} and @milk{1/4 cup}.
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
---
|
||||
title: Caramel Icing
|
||||
description:
|
||||
servings: 1
|
||||
category: lunch
|
||||
source: Unknown
|
||||
image: https://roux.roo.lol/recipe-images/caramel-icing.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- dessert
|
||||
---
|
||||
|
||||
>> Servings: 10
|
||||
|
||||
Sprinkle @sugar{1/2 cup} in a shallow heavy #3½-quart Dutch oven{}.
|
||||
|
||||
@@ -4,9 +4,14 @@ description: I think my mom lied to me about this chana salaad. She said it was
|
||||
prep_time: 10 minutes
|
||||
cook_time: 30 minutes
|
||||
servings: 6
|
||||
category: side
|
||||
category: salad
|
||||
source: OCR
|
||||
tags: salad, indian, vegan, gluten-free, instant-pot, chickpea
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- indian
|
||||
- gluten-free
|
||||
image: https://roux.roo.lol/recipe-images/chana-chickpea-salad.webp
|
||||
---
|
||||
|
||||
In the inner cooking pot of the #Instant Pot, combine the @chickpea{1%can}, @bay leaf{2}, and @water{4%cups}.
|
||||
|
||||
+7
-2
@@ -1,10 +1,15 @@
|
||||
---
|
||||
title: Chana Salad
|
||||
servings: 6
|
||||
category: side
|
||||
category: salad
|
||||
source: OCR
|
||||
description: I think my mom lied to me about this chana salaad. She said it was junk food so I would eat it, but now that I'm older I know better. After all, it's chickpeas and vegetables! But I'm glad my mom said whatever it took at time to get me to eat it, because I love this salad. It is perfect for summer and is easy to make. Serve it as a side salad or as a main dish for a vegan meal. Adjust cayenne as desired for your heat preference. SERVES 6
|
||||
image: https://kitchen.roo.lol/data/images/bd956abd-bbc8-4b7c-87e1-8c7ca70f804d.webp
|
||||
image: https://roux.roo.lol/recipe-images/chana-salad.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- indian
|
||||
- gluten-free
|
||||
---
|
||||
|
||||
In inner cooking pot of #InstantPot, combine @chickpeas{1 can, 15-16 ounces}, @bay leaves{}, and @water{}.
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
---
|
||||
title: Chicken & Pumpkin Gnocchi with Ricotta
|
||||
servings: 2
|
||||
category: main course
|
||||
tags: garnish-and-gather,gnocchi,italian,pumpkin
|
||||
category: dinner
|
||||
tags:
|
||||
- meat
|
||||
- chicken
|
||||
- pasta
|
||||
- fall
|
||||
source: garnish-and-gather
|
||||
description:
|
||||
prep_time: 35 minutesnutes
|
||||
totalTime: PT35M
|
||||
image: https://roux.roo.lol/recipe-images/chicken-and-pumpkin-gnocchi-with-ricotta.webp
|
||||
---
|
||||
|
||||
Remove any large stems from @spinach{2 1/2 cups} and roughly chop.
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
---
|
||||
title: Chickpea Butter Curry
|
||||
servings: 2
|
||||
category: main course
|
||||
category: dinner
|
||||
cuisine: Indian
|
||||
tags: chickpeas,curry,garnish-and-gather,indian,vegetarian
|
||||
tags:
|
||||
- vegetarian
|
||||
- indian
|
||||
- curry
|
||||
- vegan-option
|
||||
source: Unknown
|
||||
description: A vegetarian curry with chickpeas, cauliflower, and coconut milk, served with spiced oil drizzle
|
||||
prep_time: 20 minutes
|
||||
cook_time: 20 minutes
|
||||
image: https://roux.roo.lol/recipe-images/chickpea-butter-curry.webp
|
||||
---
|
||||
|
||||
In a small #pot{}, bring 1 1/2 cups @water{} & @basmati rice{3/4 cup} to a boil, then cover, reduce heat to low, and cook ~{12-15 minutes} or until @rice{} is tender.
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
---
|
||||
title: Chickpea Butter Curry
|
||||
servings: 2
|
||||
category: Main
|
||||
category: dinner
|
||||
source: Rebel Recipes
|
||||
image: https://roux.roo.lol/recipe-images/chickpea-butter-curry-2.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- indian
|
||||
- curry
|
||||
- vegan-option
|
||||
---
|
||||
|
||||
In a small pot, bring 1 1/2 cups water and @basmati rice{3/4%cup} to a boil, then cover, reduce heat to low, and cook 12-15 minutes or until rice is tender.
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
---
|
||||
title: Chinese Orange Shrimp
|
||||
servings: 2
|
||||
category: main course
|
||||
category: dinner
|
||||
cuisine: Chinese
|
||||
tags: chinese,garnish-and-gather,orange sauce,shrimp,stir-fry
|
||||
tags:
|
||||
- seafood
|
||||
- shrimp
|
||||
- asian
|
||||
source: Unknown
|
||||
description: Shrimp in orange sauce with brussels sprouts and carrots served over scallion rice
|
||||
prep_time: 15 minutes
|
||||
cook_time: 15 minutes
|
||||
image: https://roux.roo.lol/recipe-images/chinese-orange-shrimp.webp
|
||||
---
|
||||
|
||||
In a #pot{}, bring 1 1/2 cups @water{} & @salt{1/4 teaspoon} to a boil.
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
---
|
||||
title: Chocolate Mousse Cake with Raspberries
|
||||
servings: 24
|
||||
category: Desserts
|
||||
category: lunch
|
||||
source: King Arthur Baking
|
||||
image: https://roux.roo.lol/recipe-images/chocolate-mousse-cake-with-raspberries.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- dessert
|
||||
- chocolate
|
||||
---
|
||||
|
||||
Preheat the oven to 325°F. Lightly grease two 8" round pans (or adjust for your pan mix) and line with parchment.
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
---
|
||||
title: Cilantro Pesto Shrimp Over Orzo
|
||||
servings: 2
|
||||
category: main course
|
||||
tags: garnish-and-gather,mediterranean,orzo,pesto,shrimp
|
||||
category: dinner
|
||||
tags:
|
||||
- seafood
|
||||
- shrimp
|
||||
- pasta
|
||||
source: Unknown
|
||||
description:
|
||||
prep_time: 20 minutes
|
||||
totalTime: PT20M
|
||||
image: https://roux.roo.lol/recipe-images/cilantro-pesto-shrimp-over-orzo.webp
|
||||
---
|
||||
|
||||
Juice HALF of @lemon{1} and cut remainder into wedges.
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
---
|
||||
title: Crispy Roasted Chickpeas (Garbanzo Beans)
|
||||
servings: 2
|
||||
category: Snack
|
||||
category: appetizer
|
||||
source: Unknown
|
||||
image: https://roux.roo.lol/recipe-images/crispy-roasted-chickpeas-garbanzo-beans.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- gluten-free
|
||||
- snack
|
||||
---
|
||||
|
||||
Preheat the oven to 400°F.
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
---
|
||||
title: Crispy Tofu With Cashews and Blistered Snap Peas
|
||||
servings: 4
|
||||
category: uncategorized
|
||||
category: dinner
|
||||
source: https://cooking.nytimes.com/recipes/1021200-crispy-tofu-with-cashews-and-blistered-snap-peas?campaign_id=9&emc=edit_nn_20230415&instance_id=90280&nl=the-morning®i_id=123169868&segment_id=130511&te=1&user_id=c5863897ec3230a42bc044f493123ca2
|
||||
description: A ginger and coconut milk reduction can coat pretty much anything that browns nicely on its own. Here, it's pieces of pan-seared tofu, but small morsels of chicken and pork will work just as well. The soy and teaspoons of molasses give sauce a little caramelization, and a little shine and gloss. For a fresh side, add some blistered snap peas, tossed with sliced scallions, a little mint and a splash of rice vinegar. Snow peas, green beans, broccoli or asparagus? If it's fresh and green, it'll work just fine.
|
||||
image: https://kitchen.roo.lol/data/images/9fe7eb34-2b1c-4dba-88e8-eb4ba4abea8e.webp
|
||||
image: https://roux.roo.lol/recipe-images/crispy-tofu-with-cashews-and-blistered-snap-peas.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- asian
|
||||
- tofu
|
||||
- vegan-option
|
||||
---
|
||||
|
||||
Slice @tofu{1 (14-ounce) block, firm or extra-firm, drained} in half horizontally, and leave on #paper towels{} to dry any excess liquid.
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
---
|
||||
title: Crispy Roasted Chickpeas (Garbanzo Beans)
|
||||
servings: 2
|
||||
category: Snack
|
||||
category: appetizer
|
||||
source: Unknown
|
||||
image: https://roux.roo.lol/recipe-images/crispy-roasted-chickpeas-garbanzo-beans-2.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- gluten-free
|
||||
- snack
|
||||
---
|
||||
|
||||
Preheat the oven to 400°F.
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
title: Dried Fava Soup with Mint and Guajillo Chiles
|
||||
prep_time: 10 minutes
|
||||
cook_time: 100 minutes
|
||||
category: Soup
|
||||
category: lunch
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan-optional
|
||||
- mexican-inspired
|
||||
- beans
|
||||
- vegan
|
||||
- soup
|
||||
- mexican
|
||||
description: A slow-simmered dried fava bean soup with roasted tomatoes, garlic, and onion, finished with mint and cilantro and topped with a tangy quick-pickled guajillo chile mixture.
|
||||
source: 101 Cookbooks (adapted from Rick Bayless’s Mexican Kitchen)
|
||||
image: https://roux.roo.lol/recipe-images/dried-fava-soup.webp
|
||||
---
|
||||
|
||||
Make sure the @hulled dried fava bean{1%lb} are clean and free of dirt, then rinse them well.
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
---
|
||||
title: EASY LEMON ROSEMARY WHITE BEAN SOUP
|
||||
servings: 4
|
||||
category: Soup
|
||||
category: lunch
|
||||
source: Simple Veganista
|
||||
image: https://roux.roo.lol/recipe-images/easy-lemon-rosemary-white-bean-soup.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- soup
|
||||
- gluten-free
|
||||
---
|
||||
|
||||
Saute @olive oil{2%tbsp} in a large pot. Add @onion{1%small}, @carrots{3%large} and @celery{2 stalks}; cook for 7 minutes until softened.
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
---
|
||||
title: Easy Vegetarian Chili
|
||||
servings: 6
|
||||
category: uncategorized
|
||||
category: dinner
|
||||
source: https://www.loveandlemons.com/vegetarian-chili-recipe/
|
||||
description: You'll LOVE this easy vegetarian chili recipe! It's gluten-free, and without yogurt or sour cream on top, it's also vegan.
|
||||
image: https://kitchen.roo.lol/data/images/61187b69-c326-4ad4-aa03-f3eb7c0b1806.webp
|
||||
image: https://roux.roo.lol/recipe-images/easy-vegetarian-chili.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- chili
|
||||
- soup
|
||||
---
|
||||
|
||||
Heat @extra-virgin olive oil{2 tablespoons} in a large #pot{} over medium heat. Add @yellow onion{1 small, chopped}, a few pinches of @salt{} and @pepper{}, and stir. Cook until @onion{} is translucent, ~{5 minutes}, then add @garlic{2 cloves, minced} and @red bell pepper{1, diced}. Stir and cook until soft, ~{5 to 8 minutes}, turning heat down as needed.
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
---
|
||||
title: EASY LEMON ROSEMARY WHITE BEAN SOUP
|
||||
servings: 4
|
||||
category: Soup
|
||||
category: lunch
|
||||
source: Simple Veganista
|
||||
image: https://roux.roo.lol/recipe-images/easy-lemon-rosemary-white-bean-soup-2.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- soup
|
||||
- gluten-free
|
||||
---
|
||||
|
||||
Saute @olive oil{2%tbsp} in a large pot. Add @onion{1%small}, @carrots{3%large} and @celery{2 stalks}; cook for 7 minutes until softened.
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
---
|
||||
title: Fettucine with spiced cherry tomato sauce
|
||||
servings: 4
|
||||
category: uncategorized
|
||||
category: dinner
|
||||
source: Mealie
|
||||
description: This is basically spaghetti with a sauce made from scratch. The sauce is great and I think this whole recipe is really good especially considering the ingredients are so basic.
|
||||
|
||||
I added red pepper to the recipe for extra kick.
|
||||
image: https://kitchen.roo.lol/data/images/1a3fbf92-6d4b-4f74-adf3-0b004b5f021a.webp
|
||||
image: https://roux.roo.lol/recipe-images/fettucine-with-spiced-cherry-tomato-sauce.webp
|
||||
prep_time: 30 minutes
|
||||
cook_time: 1 hour
|
||||
tags:
|
||||
- vegetarian
|
||||
- italian
|
||||
- pasta
|
||||
---
|
||||
|
||||
Heat @olive oil{5tbsp} in a large #saute pan{} on medium-high heat.
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
---
|
||||
title: Gaja's Bulgogi Lettuce Wraps
|
||||
servings: 2
|
||||
category: main course
|
||||
tags: bulgogi,garnish-and-gather,korean,lettuce wraps
|
||||
category: dinner
|
||||
tags:
|
||||
- meat
|
||||
- asian
|
||||
- korean
|
||||
source: Unknown
|
||||
description:
|
||||
prep_time: 20 minutes
|
||||
image: https://roux.roo.lol/recipe-images/gaja-s-bulgogi-lettuce-wraps.webp
|
||||
---
|
||||
|
||||
Pat @steak bulgogi{8 oz} dry.
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
---
|
||||
title: Gochugaru Honey Salmon
|
||||
servings: 2
|
||||
category: main course
|
||||
category: dinner
|
||||
cuisine: Korean Fusion
|
||||
tags: crispy rice,garnish-and-gather,gochugaru,korean,salmon
|
||||
tags:
|
||||
- seafood
|
||||
- salmon
|
||||
- asian
|
||||
- korean
|
||||
source: Unknown
|
||||
description: Salmon with spicy honey sauce served with crispy rice, broccoli, and carrots
|
||||
prep_time: 25 minutes
|
||||
image: https://roux.roo.lol/recipe-images/gochugaru-honey-salmon.webp
|
||||
---
|
||||
|
||||
In a #pot{}, bring 1 1/2 cups @water{} to a boil for @rice{3/4 cup, long grain}.
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
title: Green Beans with Lemon and Garlic
|
||||
servings: 6
|
||||
category: Thanksgiving
|
||||
category: lunch
|
||||
source: https://www.foodnetwork.com/recipes/patrick-and-gina-neely/green-beans-with-lemon-and-garlic-recipe-1938670
|
||||
description: The Neelys' healthy Green Beans with Lemon and Garlic recipe from Food Network is the perfect, simple side for any occasion. Quickly blanch and finish on the skillet, these green beans will be on the table in less than 20 minutes.
|
||||
image: https://kitchen.roo.lol/data/images/704b15f5-0eda-4f7f-8762-1d8f36c2dea6.webp
|
||||
image: https://roux.roo.lol/recipe-images/green-beans-with-lemon-and-garlic.webp
|
||||
nutrition:
|
||||
calories: 122 kcal
|
||||
carbohydrate: 11g
|
||||
@@ -13,6 +13,11 @@ nutrition:
|
||||
protein: 3g
|
||||
sodium: 126mg
|
||||
sugar: 5g
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- gluten-free
|
||||
- side
|
||||
---
|
||||
|
||||
Blanch @green beans{2%pounds}(ends trimmed) in a large #stock pot{} of well salted boiling water until bright green in color and tender crisp, roughly ~{2%minutes}. Drain and shock in a #bowl{} of ice water to stop from cooking.
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
---
|
||||
title: Indian Spiced Chicken with Creamy Tomato Sauce & Spinach
|
||||
servings: 2
|
||||
category: main course
|
||||
tags: basmati rice,chicken,curry,garnish-and-gather,indian
|
||||
category: dinner
|
||||
tags:
|
||||
- meat
|
||||
- chicken
|
||||
- indian
|
||||
source: Unknown
|
||||
description:
|
||||
prep_time: 20 minutes
|
||||
cook_time: 20 minutes
|
||||
image: https://roux.roo.lol/recipe-images/indian-spiced-chicken-2.webp
|
||||
---
|
||||
|
||||
Bring 1 1/2 cups @water{} & @salt{1/4 tsp} to a boil in a #pot{}.
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
---
|
||||
title: Italian Sausage & Delicata Rigatoni
|
||||
description:
|
||||
servings: 1
|
||||
category: dinner
|
||||
source: Unknown
|
||||
image: https://roux.roo.lol/recipe-images/italian-sausage-and-delicata-rigatoni.webp
|
||||
tags:
|
||||
- meat
|
||||
- sausage
|
||||
- pasta
|
||||
- italian
|
||||
---
|
||||
|
||||
>> title: Italian Sausage & Delicata Rigatoni
|
||||
>> author: Chef Robin Pridgen
|
||||
>> servings: 2
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
---
|
||||
title: Kale & Chestnut Stew with Butternut Squash, Wild Rice & Parsley–Walnut Pesto
|
||||
servings: 2
|
||||
category: main dish
|
||||
category: dinner
|
||||
cuisine: American
|
||||
tags: stew,kale,butternut-squash,wild-rice,parsley,walnut,pesto,dairy,garnish-and-gather
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan-option
|
||||
- soup
|
||||
- fall
|
||||
source: Garnish & Gather — Chef Emily Golub
|
||||
description: Cozy kale-and-chestnut stew finished with cream and topped with a bright parsley–walnut pesto. Allergens: dairy, walnuts.
|
||||
prep_time: 20 minutes
|
||||
cook_time: 35 minutes
|
||||
image: https://roux.roo.lol/recipe-images/kale-and-chestnut-stew.webp
|
||||
---
|
||||
|
||||
Remove any large stems from @kale{3 c} and thinly slice into ribbons.
|
||||
|
||||
+7
-1
@@ -1,9 +1,15 @@
|
||||
---
|
||||
title: Spicy & Crunchy Garlic Tofu (Kkanpoong Tofu)
|
||||
servings: 2
|
||||
category: uncategorized
|
||||
category: dinner
|
||||
source: https://thekoreanvegan.com/spicy-crunchy-garlic-tofu-kkampoong-tofu/
|
||||
description:
|
||||
image: https://roux.roo.lol/recipe-images/kkanpoong-tofu.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- asian
|
||||
- korean
|
||||
- tofu
|
||||
---
|
||||
|
||||
To a large #bowl{}, add a pinch of @salt{} and 2 tablespoons of @potato starch{} to @tofu{1 16 ounce block, extra firm, pressed and chopped into 1/2 inch bite sized chunks}. Gently toss @tofu{} to make sure they are all evenly coated. Set aside.
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
---
|
||||
title: Korean Shrimp Tacos
|
||||
servings: 2
|
||||
category: main course
|
||||
category: dinner
|
||||
cuisine: Korean-Mexican Fusion
|
||||
tags: fusion,garnish-and-gather,korean,shrimp,tacos
|
||||
tags:
|
||||
- seafood
|
||||
- shrimp
|
||||
- asian
|
||||
- korean
|
||||
- mexican
|
||||
source: Unknown
|
||||
description: Crispy shrimp tacos with gochujang sauce and bok choy rice salad
|
||||
prep_time: 25 minutes
|
||||
cook_time: 25 minutes
|
||||
image: https://roux.roo.lol/recipe-images/korean-shrimp-tacos.webp
|
||||
---
|
||||
|
||||
In a #pot{}, bring 1 1/2 cups @water{} & @salt{1/4 teaspoon} to a boil.
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
---
|
||||
title: Kung Pao Shrimp with Sichuan Roasted Green Beans
|
||||
servings: 2
|
||||
category: main course
|
||||
tags: chinese,garnish-and-gather,kung pao,shrimp
|
||||
category: dinner
|
||||
tags:
|
||||
- seafood
|
||||
- shrimp
|
||||
- asian
|
||||
- chinese
|
||||
source: Unknown
|
||||
description:
|
||||
prep_time: 25 minutes
|
||||
image: https://roux.roo.lol/recipe-images/kung-pao-shrimp-with-sichuan-roasted-green-beans.webp
|
||||
---
|
||||
|
||||
Preheat #oven{} to 425°.
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
---
|
||||
title: Lactation cookies
|
||||
servings: 1
|
||||
category: Cookies
|
||||
tags: cookies
|
||||
category: lunch
|
||||
tags:
|
||||
- vegetarian
|
||||
- dessert
|
||||
- snack
|
||||
source: Mealie
|
||||
description:
|
||||
image: https://roux.roo.lol/recipe-images/lactation-cookies.webp
|
||||
---
|
||||
|
||||
Preheat #oven{} to 350°.
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
---
|
||||
title: Lactation Cookies
|
||||
servings: 12
|
||||
category: Cookies
|
||||
category: lunch
|
||||
source: Mealie
|
||||
image: https://roux.roo.lol/recipe-images/lactation-cookies-2.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- dessert
|
||||
- snack
|
||||
---
|
||||
|
||||
@butter{1%cup}
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
---
|
||||
title: Lentil Soup with Lemon and Turmeric
|
||||
servings: 8
|
||||
category: uncategorized
|
||||
category: lunch
|
||||
source: https://www.inspiredtaste.net/40400/lentil-soup/
|
||||
description: This is our favorite lentil soup recipe. I make this soup with lentils, carrots, turmeric, and fresh lemon whenever we are in need of some healthy comfort food. It's savory, satisfying, and utterly delicious. I like quite a bit of fresh lemon in this soup and actually serve soup with slices of lemon. This adds even more lemony flavor and shows that lemon was added to soup.
|
||||
image: https://kitchen.roo.lol/data/images/47a68b65-c916-4c5d-86f2-ea989ea310a7.webp
|
||||
image: https://roux.roo.lol/recipe-images/lentil-soup-with-lemon-and-turmeric.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- soup
|
||||
- gluten-free
|
||||
- indian
|
||||
---
|
||||
|
||||
Heat @olive oil{2 tablespoons} in a large #pot{} (like a #Dutch oven{}) over medium heat. Add @onions{2 cups, finely chopped (1 medium onion)}, @carrot{1 1/2 cups, chopped (2 large carrots)}, and @salt{1/4 teaspoon} of and cook, stirring occasionally until they are softened and starting to smell sweet, about ~{5 to 7 minutes}. Stir in @garlic{2 teaspoons, minced (3 cloves)}, @ground cumin{1 teaspoon}, @ground coriander{1/2 teaspoon}, @fresh ground black pepper{1/2 teaspoon}, and @ground turmeric{1/2 teaspoon} and cook for ~{30 seconds}.
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
---
|
||||
title: Moroccan Spiced Lentil & Chickpea Soup
|
||||
servings: 1
|
||||
category: Soup
|
||||
category: lunch
|
||||
source: Rebel Recipes
|
||||
image: https://roux.roo.lol/recipe-images/moroccan-spiced-lentil-chickpea-soup.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- soup
|
||||
- moroccan
|
||||
---
|
||||
|
||||
In a large pan, add @olive oil{2%tbsp} and @onion{1%large}(chopped); cook on medium heat for 7-8 minutes until soft and browning.
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
---
|
||||
title: Moroccan Spiced Lentil & Chickpea Soup
|
||||
servings: 1
|
||||
category: Soup
|
||||
category: lunch
|
||||
source: Rebel Recipes
|
||||
image: https://roux.roo.lol/recipe-images/moroccan-spiced-lentil-and-chickpea-soup.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- soup
|
||||
- moroccan
|
||||
---
|
||||
|
||||
In a large pan, add @olive oil{2%tbsp} and @onion{1%large}(chopped); cook on medium heat for 7-8 minutes until soft and browning.
|
||||
|
||||
+5
-4
@@ -1,13 +1,14 @@
|
||||
---
|
||||
title: Orange Rolls
|
||||
servings: 8
|
||||
category: uncategorized
|
||||
category: lunch
|
||||
source: https://cooking.nytimes.com/recipes/1023939-orange-rolls
|
||||
description: "Citrusy cinnamon rolls made without yeast, perfumed with fresh orange zest and finished with an orange-cream cheese glaze."
|
||||
tags:
|
||||
- breakfast
|
||||
- rolls
|
||||
- orange
|
||||
- vegetarian
|
||||
- dessert
|
||||
- bread
|
||||
image: https://roux.roo.lol/recipe-images/orange-rolls.webp
|
||||
---
|
||||
|
||||
Make the dough: Heat the #oven to 375°F. Place @light brown sugar{1/3%cup} in a small #bowl and zest the @orange{1} directly over it. Gently rub the zest into the sugar and transfer 2 tablespoons/25 grams of the orange sugar to a large #bowl to use for the dough. Save the rest of the orange sugar for the filling.
|
||||
|
||||
+6
-1
@@ -1,8 +1,13 @@
|
||||
---
|
||||
title: Palak Paneer
|
||||
servings: 4
|
||||
category: Indian
|
||||
category: dinner
|
||||
source: Mealie
|
||||
image: https://roux.roo.lol/recipe-images/palak-paneer.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- indian
|
||||
- paneer
|
||||
---
|
||||
|
||||
Preheat the #Instant Pot by selecting Sauté and adjust to More for high heat. When the inner cooking pot is hot, add @peanut oil{2%tbsp} and heat until it is shimmering. Add @garlic cloves{5}(minced), @ginger{2%tsp}(minced), and @serrano or jalapeño chile{1}(minced), and sauté for ~{1%minute} or until fragrant.
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
---
|
||||
title: Passion Fruit Curd Recipe
|
||||
servings: 10
|
||||
category: uncategorized
|
||||
category: lunch
|
||||
source: https://www.theflavorbender.com/make-passion-fruit-curd/#recipe
|
||||
description: Makes about 1 1/4 cups of passion fruit curd
|
||||
image: https://kitchen.roo.lol/data/images/803d123d-d98f-4a68-a820-e47c88b7a66a.webp
|
||||
image: https://roux.roo.lol/recipe-images/passion-fruit-curd-recipe.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- gluten-free
|
||||
- dessert
|
||||
---
|
||||
|
||||
Indirect Heat Method
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
---
|
||||
title: Pecan-Brown Butter Trout
|
||||
servings: 2
|
||||
category: main course
|
||||
category: dinner
|
||||
cuisine: American
|
||||
tags: brown butter,garnish-and-gather,pecan,sweet potatoes,trout
|
||||
tags:
|
||||
- seafood
|
||||
- fish
|
||||
source: Unknown
|
||||
description: Pan-seared trout with pecan-brown butter sauce, served with herbed sweet potatoes, radishes, and wilted greens
|
||||
prep_time: 30 minutes
|
||||
cook_time: 30 minutes
|
||||
image: https://roux.roo.lol/recipe-images/pecan-brown-butter-trout.webp
|
||||
---
|
||||
|
||||
Preheat #oven{} to 400°F.
|
||||
|
||||
+7
-2
@@ -1,13 +1,18 @@
|
||||
---
|
||||
title: Ribollita - The Tuscan Stew you Should be Eating Regularly
|
||||
servings: 10
|
||||
category: uncategorized
|
||||
category: lunch
|
||||
source: https://www.101cookbooks.com/ribollita-recipe/
|
||||
description: Ribollita, a beautifully thick Tuscan stew made with dark greens, lots of beans, vegetables, olive oil, and thickened with day-old bread. One of my favorites.
|
||||
image: https://kitchen.roo.lol/data/images/4bc005e2-168b-43bb-a75e-a463eb5bbdc6.webp
|
||||
image: https://roux.roo.lol/recipe-images/ribollita.webp
|
||||
prep_time: 15 minutes
|
||||
cook_timee:25 minutesinutes
|
||||
totalTime: PT40M
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- soup
|
||||
- italian
|
||||
---
|
||||
|
||||
In your largest thick-bottomed #pot{} over medium heat combine the @olive oil{3 tbsp + more for drizzling}, @celery{4 stalks, chopped}, @garlic{3 medium cloves, chopped}, @carrots{2 medium or equiv winter squash, chopped}, and @red onion{1 medium, chopped}. Cook for ~{10-15 minutes} sweating the vegetables, but avoid any browning. Stir in the @crushed tomatoes{14oz can} and @red pepper flakes{1/2 tsp, crushed}, and simmer for another ~{10 minutes} or so, long enough for the tomatoes to thicken up a bit. Stir in the @kale{1lb, stems trimmed off + leaves chopped}, @white beans{3 cups / 22oz cooked}, and @water{8 cups / 2 liters}. Bring to a boil, reduce the heat, and simmer until the greens are tender, about ~{15 minutes}.
|
||||
|
||||
@@ -4,9 +4,14 @@ description: Pumpkin season is in full swing, and this pumpkin sage soup is the
|
||||
prep_time: 5 minutes
|
||||
cook_time: 1 hour 40 minutes
|
||||
servings: 5
|
||||
category: uncategorized
|
||||
category: lunch
|
||||
source: https://www.skinnytaste.com/roasted-pumpkin-sage-soup
|
||||
tags: soup, pumpkin, fall, vegetarian-option, gluten-free
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- soup
|
||||
- fall
|
||||
image: https://roux.roo.lol/recipe-images/roasted-pumpkin-sage.webp
|
||||
---
|
||||
|
||||
Heat the oven to 400°F.
|
||||
|
||||
+6
-1
@@ -1,8 +1,13 @@
|
||||
---
|
||||
title: Saag Paneer
|
||||
servings: 1
|
||||
category: Indian
|
||||
category: dinner
|
||||
source: Mealie
|
||||
image: https://roux.roo.lol/recipe-images/saag-paneer.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- indian
|
||||
- paneer
|
||||
---
|
||||
|
||||
Preheat the #Instant Pot by selecting Sauté and adjust to More for high heat. When the inner cooking pot is hot, add @peanut oil{2%tbsp} and heat until it is shimmering. Add @garlic cloves{5}(minced), @ginger{2%tsp}(minced), and @serrano or jalapeno chile{1}(minced), and sauté for ~{1%minute} or until fragrant.
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
---
|
||||
title: Sauteed Asparagus with Lemon
|
||||
servings: 4
|
||||
category: Thanksgiving
|
||||
category: lunch
|
||||
source: https://www.marthastewart.com/316639/sauteed-asparagus-with-lemon
|
||||
description:
|
||||
image: https://kitchen.roo.lol/data/images/15498ca3-db5d-461f-a3b7-ce6674fc6bea.webp
|
||||
image: https://roux.roo.lol/recipe-images/sauteed-asparagus-with-lemon.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- gluten-free
|
||||
- side
|
||||
---
|
||||
|
||||
In a large #skillet{}, heat @olive oil{1 tablespoon} over medium. Add @asparagus{2 bunches, 14 ounces each, ends trimmed}, and season with @coarse salt{} and @ground pepper{}. Cook, tossing occasionally, until lightly browned and crisp-tender, ~{10 to 15 minutes}, depending on thickness. Add @lemon zest{4 wide strips} and cook ~{1 minute}. Add @lemon juice{from 1/2 lemon}, toss to coat, and serve warm.
|
||||
@@ -1,10 +1,15 @@
|
||||
---
|
||||
title: Slow Cooker Chicken Shawarma
|
||||
servings: 6
|
||||
category: uncategorized
|
||||
category: dinner
|
||||
source: https://acleanbake.com/slow-cooker-chicken-shawarma/
|
||||
description: Slow cooker chicken shawarma combines the ease of a slow cooker meal with the complex flavor of a slow-roasted middle eastern chicken feast! This is a quick version of middle eastern favorite: the slow cooker does a lot of the work and it can be prepped ahead of time. Various serving options make this sensational paleo and gluten free dinner recipe adaptable for low carb and Whole30 compliance, too!
|
||||
image: https://kitchen.roo.lol/data/images/0cea2c65-f549-4f57-97fd-5a1d23941297.webp
|
||||
image: https://roux.roo.lol/recipe-images/slow-cooker-chicken-shawarma.webp
|
||||
tags:
|
||||
- meat
|
||||
- chicken
|
||||
- mediterranean
|
||||
- middle-eastern
|
||||
---
|
||||
|
||||
Place @chicken breasts{2 lbs, boneless, skinless} in #bowl{} of your #slow cooker{}, and add @water{1/2 cup}. Cook on high ~{2 hours} until @chicken{} is cooked through (the juices run clear and @fat{} has rendered out of @chicken{}).
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
---
|
||||
title: Southern Macaroni and Cheese
|
||||
servings: 10
|
||||
category: Thanksgiving
|
||||
category: lunch
|
||||
source: https://cooking.nytimes.com/recipes/1020515-southern-macaroni-and-cheese
|
||||
description: There is macaroni and cheese, and then there is special occasion macaroni and cheese like this one. Unlike most recipes, which start with a roux, this one begins with a milk-and-egg base, which gives the dish an incredibly rich, silky taste. It's adapted from Millie Peartree, the owner of Millie Peartree Fish Fry & Soul Food restaurant in the Bronx, who has been making this dish since she was a little girl. The recipe was passed down in her family for generations, but because of the generous amount of cheese used, the dish was only made for events like Christmas and Thanksgiving. Extra-sharp Cheddar adds tartness and a layer of Colby Jack creates a gooey, molten center. If you can't find a Colby Jack blend, shredded mozzarella or a Mexican-style blend will work in its place.
|
||||
image: https://kitchen.roo.lol/data/images/657048ab-342f-4ca3-84cb-fe9dcb3ae2a4.webp
|
||||
image: https://roux.roo.lol/recipe-images/southern-macaroni-and-cheese.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- southern
|
||||
- pasta
|
||||
---
|
||||
|
||||
Heat #oven{} to 350 degrees. Bring a large #pot{} of generously salted water to a boil. Add @elbow macaroni{1%pound} and cook according to package directions until a little under al dente, about ~{4%minutes}. Transfer to a #colander{} and rinse under cold water to stop cooking. Set aside.
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
---
|
||||
title: Spaghetti Carbonara with Bacon & Crisp Lettuce Salad
|
||||
servings: 2
|
||||
category: main dish
|
||||
category: dinner
|
||||
cuisine: Italian
|
||||
tags: bacon,carbonara,garnish-and-gather,italian,pasta,salad
|
||||
tags:
|
||||
- meat
|
||||
- italian
|
||||
- pasta
|
||||
source: Unknown
|
||||
description:
|
||||
prep_time: 15 minutes
|
||||
cook_time: 15 minutes
|
||||
image: https://roux.roo.lol/recipe-images/spaghetti-carbonara.webp
|
||||
---
|
||||
|
||||
Trim root from @lettuce{1 head} and separate into leaves.
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
---
|
||||
title: Spaghetti Squash w/ Chickpeas & Kale
|
||||
servings: 3
|
||||
category: Main
|
||||
category: dinner
|
||||
source: Love and Lemons
|
||||
image: https://roux.roo.lol/recipe-images/spaghetti-squash-w-chickpeas-and-kale.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- gluten-free
|
||||
- vegetable
|
||||
---
|
||||
Preheat the oven to 400F.
|
||||
Prepare your squash following the directions in this post.
|
||||
|
||||
+6
-1
@@ -1,8 +1,13 @@
|
||||
---
|
||||
title: Skillet Spanakopita (Greek Spinach Feta Pie)
|
||||
servings: 8
|
||||
category: Greek
|
||||
category: dinner
|
||||
source: From a Chef's Kitchen
|
||||
image: https://roux.roo.lol/recipe-images/spanakopita.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- mediterranean
|
||||
- greek
|
||||
---
|
||||
|
||||
Preheat the oven to 375 degrees.
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
title: Spicy Shrimp Paella
|
||||
servings: 2
|
||||
prep_time: 25 minutes
|
||||
category: main course
|
||||
category: dinner
|
||||
tags:
|
||||
- chorizo
|
||||
- paella
|
||||
- seafood
|
||||
- shrimp
|
||||
- spanish
|
||||
- rice
|
||||
image: https://roux.roo.lol/recipe-images/spicy-shrimp-paella.webp
|
||||
---
|
||||
|
||||
In a large sauté #pan, cook @chorizo{2%oz} over medium for ~{3-5%minutes}, until cooked through, then remove, keeping drippings in the pan.
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
---
|
||||
title: Strawberry and Wild Rice Tartlets
|
||||
servings: 12
|
||||
category: uncategorized
|
||||
category: appetizer
|
||||
source: https://www.bonappetit.com/recipe/strawberry-wild-rice-tartlets
|
||||
description: This simple six-ingredient dessert is vegan and bursting with texture (crispy wild rice crust) and flavor (rehydrated cherries with fresh strawberries for the filling).
|
||||
image: https://kitchen.roo.lol/data/images/ed78da5c-1aa5-4d0b-90bc-4d317b7f7b89.webp
|
||||
image: https://roux.roo.lol/recipe-images/strawberry-and-wild-rice-tartlets.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- gluten-free-option
|
||||
- dessert
|
||||
---
|
||||
|
||||
Heat #oven{} to 350°. Brush the cups of one standard 12-cup #muffin pan{} or two standard 6-cup #muffin pans{} with @vegetable oil{}. Bring 6 cups lightly salted @water{} to a boil in a medium #saucepan{}. Add @wild rice{1 cup}, cover, and cook until @water{} evaporates and grains split open, about ~{1½ hours}. Spread out @rice{} on a large rimmed #baking sheet{} and let cool. Reserve #pan{}.
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
---
|
||||
title: Thai Veggie Pad See Ew
|
||||
servings: 2
|
||||
category: Thai
|
||||
category: dinner
|
||||
source: Unknown
|
||||
image: https://roux.roo.lol/recipe-images/thai-veggie-pad-see-ew.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan-option
|
||||
- asian
|
||||
- thai
|
||||
- noodles
|
||||
---
|
||||
|
||||
Bring water to a boil for rice noodles.
|
||||
|
||||
+6
-2
@@ -1,12 +1,16 @@
|
||||
---
|
||||
title: Tiramisu
|
||||
servings: 1
|
||||
category: uncategorized
|
||||
category: lunch
|
||||
source: Unknown
|
||||
description: Georgie Grasso recipe - GGBO winner 2024 with this recipe featured!
|
||||
image: https://kitchen.roo.lol/data/images/d15cdb2C-71db-42d7-a603-a29389e13bd3.webp
|
||||
image: https://roux.roo.lol/recipe-images/tiramisu.webp
|
||||
prep_time: 15 minutes
|
||||
cook_time: 30 minutes
|
||||
tags:
|
||||
- vegetarian
|
||||
- italian
|
||||
- dessert
|
||||
---
|
||||
|
||||
Set #oven{} to 355F.
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
---
|
||||
title: Warm Up With a Quick Lemony Chicken Soup
|
||||
servings: 6
|
||||
category: Soup
|
||||
category: lunch
|
||||
source: EatingWell
|
||||
image: https://roux.roo.lol/recipe-images/warm-up-with-a-quick-lemony-chicken-soup.webp
|
||||
tags:
|
||||
- meat
|
||||
- chicken
|
||||
- soup
|
||||
- gluten-free
|
||||
---
|
||||
|
||||
Heat @olive oil{1%tbsp} in a Dutch oven over medium-high. Add @carrots{3/4%cup} and @yellow onion{1/2%cup}; cook, stirring often, until vegetables are softened, 3 to 4 minutes. Add @garlic cloves{2}(minced) and @crushed red pepper flakes{3/4%tsp}, and cook, stirring constantly, until fragrant, about 1 minute.
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
---
|
||||
title: West African Peanut Chicken
|
||||
servings: 2
|
||||
category: main course
|
||||
tags: chicken,garnish-and-gather,peanut,west african
|
||||
category: dinner
|
||||
tags:
|
||||
- meat
|
||||
- chicken
|
||||
- african
|
||||
- stew
|
||||
source: Unknown
|
||||
description:
|
||||
prep_time: 25 minutes
|
||||
cook_time: 25 minutes
|
||||
image: https://roux.roo.lol/recipe-images/west-african-peanut-chicken.webp
|
||||
---
|
||||
|
||||
Mince @fresno pepper{1}—remove seeds and use only half for less heat.
|
||||
|
||||
@@ -3,7 +3,11 @@ title: Wheat Berry Salad
|
||||
servings: 1
|
||||
description: This healthy wheat berry salad is a delicious fall side dish or lunch with dried cranberries, fresh herbs, walnuts, and a tangy vinaigrette.
|
||||
source: https://www.loveandlemons.com/wheat-berry-salad/
|
||||
image: https://kitchen.roo.lol/data/images/eb72067c-28c7-4ae3-88ed-1fc0b9aba762.webp
|
||||
image: https://roux.roo.lol/recipe-images/wheat-berry-salad.webp
|
||||
tags:
|
||||
- vegetarian
|
||||
- vegan
|
||||
- grain-bowl
|
||||
---
|
||||
|
||||
= Wheat berries =
|
||||
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
while IFS='|' read -r file slug; do
|
||||
url=$(grep "^image:" "$file" | sed 's/^image:[[:space:]]*//')
|
||||
echo "[$slug] Downloading from $url"
|
||||
curl -sL -o "recipe-images/$slug.webp" "$url" || echo "[$slug] DOWNLOAD FAILED"
|
||||
done
|
||||
@@ -0,0 +1,409 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Categorize recipes with category and tags based on content."""
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
import yaml
|
||||
|
||||
WORKDIR = Path("/home/jbrechtel/recipes")
|
||||
|
||||
# Recipe classifications: (filename_pattern, {category, tags})
|
||||
CLASSIFICATIONS = [
|
||||
("Alain-Ducase-Bread.cook", {
|
||||
"category": "appetizer",
|
||||
"tags": ["vegetarian", "french", "cheese"]
|
||||
}),
|
||||
("Apple Cider Vinegar Dressing.cook", {
|
||||
"category": "salad",
|
||||
"tags": ["vegetarian", "vegan", "gluten-free"]
|
||||
}),
|
||||
("Arugula Pesto Chicken with Pecans & Roasted Butternut.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["meat", "chicken", "pasta"]
|
||||
}),
|
||||
("Arugula Pesto Wheat Berries Recipe.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "grain-bowl"]
|
||||
}),
|
||||
("Asparagus Salad.cook", {
|
||||
"category": "salad",
|
||||
"tags": ["vegetarian", "gluten-free", "spring"]
|
||||
}),
|
||||
("Authentic Cacio e Pepe Recipe.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "italian", "pasta"]
|
||||
}),
|
||||
("Baked Chile Relleno with Black Beans.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "mexican", "beans"]
|
||||
}),
|
||||
("Bang Bang Tofu.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "vegan-option", "asian", "tofu"]
|
||||
}),
|
||||
("Barbecue Chicken with Crispy Pan-Fried Okra & Cheddar Biscuits.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["meat", "chicken", "southern"]
|
||||
}),
|
||||
("Black Bean and Corn Salad.cook", {
|
||||
"category": "salad",
|
||||
"tags": ["vegetarian", "vegan", "gluten-free", "mexican"]
|
||||
}),
|
||||
("Black Pepper Tofu (Ottolenghi).cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "asian", "tofu", "vegan-option"]
|
||||
}),
|
||||
("Black Pepper Tofu with Bok Choy.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "asian", "tofu", "vegan-option"]
|
||||
}),
|
||||
("Blackened Shrimp and Gouda Grits.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["seafood", "shrimp", "southern"]
|
||||
}),
|
||||
("Butter Tofu.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "indian", "tofu"]
|
||||
}),
|
||||
("butternut-turkey-chili.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["meat", "turkey", "chili", "soup"]
|
||||
}),
|
||||
("Butternut Turkey Chili.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["meat", "turkey", "chili", "soup"]
|
||||
}),
|
||||
("BUTTERNUT & TURKEY CHILI with sweet peppers, spiced pepitas & queso fresco.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["meat", "turkey", "chili", "soup"]
|
||||
}),
|
||||
("Cabbage_Salad.cook", {
|
||||
"category": "salad",
|
||||
"tags": ["vegetarian", "vegan", "gluten-free"]
|
||||
}),
|
||||
("Cajun Shrimp and Sausage Vegetable Foil Packets.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["seafood", "shrimp", "cajun", "sausage"]
|
||||
}),
|
||||
("Cajun Spice Mix (for pecans, oysters, etc).cook", {
|
||||
"category": "appetizer",
|
||||
"tags": ["vegetarian", "vegan", "spice-blend"]
|
||||
}),
|
||||
("Caramel Cake.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "dessert"]
|
||||
}),
|
||||
("Caramel Icing.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "dessert"]
|
||||
}),
|
||||
("cauliflower-milanese.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "italian", "vegetable"]
|
||||
}),
|
||||
("Chana Chickpea Salad.cook", {
|
||||
"category": "salad",
|
||||
"tags": ["vegetarian", "vegan", "indian", "gluten-free"]
|
||||
}),
|
||||
("Chana Salad.cook", {
|
||||
"category": "salad",
|
||||
"tags": ["vegetarian", "vegan", "indian", "gluten-free"]
|
||||
}),
|
||||
("Chicken and Pumpkin Gnocchi with Ricotta.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["meat", "chicken", "pasta", "fall"]
|
||||
}),
|
||||
("Chickpea Butter Curry.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "indian", "curry", "vegan-option"]
|
||||
}),
|
||||
("Chickpea_Butter_Curry.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "indian", "curry", "vegan-option"]
|
||||
}),
|
||||
("Chinese Orange Shrimp.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["seafood", "shrimp", "asian"]
|
||||
}),
|
||||
("Chocolate_Mousse_Cake_with_Raspberries.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "dessert", "chocolate"]
|
||||
}),
|
||||
("Cilantro Pesto Shrimp Over Orzo.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["seafood", "shrimp", "pasta"]
|
||||
}),
|
||||
("Crispy Roasted Chickpeas (Garbanzo Beans).cook", {
|
||||
"category": "appetizer",
|
||||
"tags": ["vegetarian", "vegan", "gluten-free", "snack"]
|
||||
}),
|
||||
("Crispy_Roasted_Chickpeas_(Garbanzo_Beans).cook", {
|
||||
"category": "appetizer",
|
||||
"tags": ["vegetarian", "vegan", "gluten-free", "snack"]
|
||||
}),
|
||||
("Crispy Tofu With Cashews and Blistered Snap Peas.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "asian", "tofu", "vegan-option"]
|
||||
}),
|
||||
("Dried Fava Soup.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "vegan", "soup", "mexican"]
|
||||
}),
|
||||
("Easy Lemon Rosemary White Bean Soup.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "vegan", "soup", "gluten-free"]
|
||||
}),
|
||||
("Easy_Lemon_Rosemary_White_Bean_Soup.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "vegan", "soup", "gluten-free"]
|
||||
}),
|
||||
("Easy Vegetarian Chili.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "vegan", "chili", "soup"]
|
||||
}),
|
||||
("example-fried-rice.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["meat", "asian", "rice"]
|
||||
}),
|
||||
("Fettucine with spiced cherry tomato sauce.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "italian", "pasta"]
|
||||
}),
|
||||
("Gaja's Bulgogi Lettuce Wraps.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["meat", "asian", "korean"]
|
||||
}),
|
||||
("Gochugaru Honey Salmon.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["seafood", "salmon", "asian", "korean"]
|
||||
}),
|
||||
("Green_Beans_with_Lemon_and_Garlic.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "vegan", "gluten-free", "side"]
|
||||
}),
|
||||
("indian-spiced-chicken.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["meat", "chicken", "indian"]
|
||||
}),
|
||||
("Indian Spiced Chicken.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["meat", "chicken", "indian"]
|
||||
}),
|
||||
("Italian Sausage and Delicata Rigatoni.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["meat", "sausage", "pasta", "italian"]
|
||||
}),
|
||||
("Kale and Chestnut Stew.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "vegan-option", "soup", "fall"]
|
||||
}),
|
||||
("Kkanpoong Tofu.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "asian", "korean", "tofu"]
|
||||
}),
|
||||
("Korean Shrimp Tacos.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["seafood", "shrimp", "asian", "korean", "mexican"]
|
||||
}),
|
||||
("Kung Pao Shrimp with Sichuan Roasted Green Beans.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["seafood", "shrimp", "asian", "chinese"]
|
||||
}),
|
||||
("Lactation cookies.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "dessert", "snack"]
|
||||
}),
|
||||
("Lactation_cookies.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "dessert", "snack"]
|
||||
}),
|
||||
("Lentil Soup with Lemon and Turmeric.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "vegan", "soup", "gluten-free", "indian"]
|
||||
}),
|
||||
("Moroccan_Spiced_Lentil_and_Chickpea_Soup.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "vegan", "soup", "moroccan"]
|
||||
}),
|
||||
("Moroccan Spiced Lentil & Chickpea Soup.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "vegan", "soup", "moroccan"]
|
||||
}),
|
||||
("Orange Rolls.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "dessert", "bread"]
|
||||
}),
|
||||
("Palak Paneer.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "indian", "paneer"]
|
||||
}),
|
||||
("Passion Fruit Curd Recipe.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "gluten-free", "dessert"]
|
||||
}),
|
||||
("Pecan-Brown Butter Trout.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["seafood", "fish"]
|
||||
}),
|
||||
("Ribollita.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "vegan", "soup", "italian"]
|
||||
}),
|
||||
("Roasted Pumpkin Sage.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "vegan", "soup", "fall"]
|
||||
}),
|
||||
("Saag Paneer.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "indian", "paneer"]
|
||||
}),
|
||||
("Sauteed Asparagus with Lemon.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "vegan", "gluten-free", "side"]
|
||||
}),
|
||||
("sheet-pan-miso-chicken-with-radishes-and-lime.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["meat", "chicken", "asian", "japanese"]
|
||||
}),
|
||||
("sheet-pan-shrimp-oreganata.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["seafood", "shrimp", "italian"]
|
||||
}),
|
||||
("shrimp-feta.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["seafood", "shrimp", "mediterranean", "pasta"]
|
||||
}),
|
||||
("Slow Cooker Chicken Shawarma.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["meat", "chicken", "mediterranean", "middle-eastern"]
|
||||
}),
|
||||
("Southern_Macaroni_and_Cheese.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "southern", "pasta"]
|
||||
}),
|
||||
("Spaghetti Carbonara.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["meat", "italian", "pasta"]
|
||||
}),
|
||||
("Spaghetti_Squash_w_Chickpeas_and_Kale.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "vegan", "gluten-free", "vegetable"]
|
||||
}),
|
||||
("Spanakopita.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "mediterranean", "greek"]
|
||||
}),
|
||||
("Spicy Shrimp Paella.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["seafood", "shrimp", "spanish", "rice"]
|
||||
}),
|
||||
("Strawberry and Wild Rice Tartlets.cook", {
|
||||
"category": "appetizer",
|
||||
"tags": ["vegetarian", "gluten-free-option", "dessert"]
|
||||
}),
|
||||
("Thai_Veggie_Pad_See_Ew.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["vegetarian", "vegan-option", "asian", "thai", "noodles"]
|
||||
}),
|
||||
("Tiramisu.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["vegetarian", "italian", "dessert"]
|
||||
}),
|
||||
("Warm Up With a Quick Lemony Chicken Soup.cook", {
|
||||
"category": "lunch",
|
||||
"tags": ["meat", "chicken", "soup", "gluten-free"]
|
||||
}),
|
||||
("West African Peanut Chicken.cook", {
|
||||
"category": "dinner",
|
||||
"tags": ["meat", "chicken", "african", "stew"]
|
||||
}),
|
||||
("Wheat Berry Salad.cook", {
|
||||
"category": "salad",
|
||||
"tags": ["vegetarian", "vegan", "grain-bowl"]
|
||||
}),
|
||||
]
|
||||
|
||||
|
||||
def update_frontmatter(filepath, classification):
|
||||
"""Update category and tags in the YAML frontmatter."""
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
|
||||
# Find the YAML frontmatter
|
||||
m = re.match(r'^---\n(.*?)\n---', content, re.DOTALL)
|
||||
if not m:
|
||||
print(f" No frontmatter in {filepath.name}")
|
||||
return False
|
||||
|
||||
fm_text = m.group(1)
|
||||
lines = fm_text.split('\n')
|
||||
|
||||
new_lines = []
|
||||
in_tags = False
|
||||
category_updated = False
|
||||
tags_updated = False
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
|
||||
if stripped.startswith('category:'):
|
||||
new_lines.append(f"category: {classification['category']}")
|
||||
category_updated = True
|
||||
in_tags = False
|
||||
elif stripped.startswith('tags:'):
|
||||
# Skip old tags, we'll add new ones
|
||||
in_tags = True
|
||||
tags_updated = True
|
||||
new_lines.append("tags:")
|
||||
for tag in classification['tags']:
|
||||
new_lines.append(f" - {tag}")
|
||||
elif in_tags:
|
||||
# Skip old tag lines (indented with -)
|
||||
if stripped.startswith('-') or stripped == '':
|
||||
continue
|
||||
else:
|
||||
in_tags = False
|
||||
new_lines.append(line)
|
||||
else:
|
||||
new_lines.append(line)
|
||||
|
||||
if not category_updated:
|
||||
# Add category before tags or at end of frontmatter
|
||||
print(f" No category field in {filepath.name}")
|
||||
|
||||
if not tags_updated:
|
||||
# Add tags at end of frontmatter
|
||||
new_lines.append("tags:")
|
||||
for tag in classification['tags']:
|
||||
new_lines.append(f" - {tag}")
|
||||
|
||||
new_fm = '\n'.join(new_lines)
|
||||
new_content = f"---\n{new_fm}\n---" + content[m.end():]
|
||||
filepath.write_text(new_content, encoding="utf-8")
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
updated = 0
|
||||
errors = 0
|
||||
|
||||
for filename, classification in CLASSIFICATIONS:
|
||||
fp = WORKDIR / filename
|
||||
if not fp.exists():
|
||||
print(f"NOT FOUND: {filename}")
|
||||
errors += 1
|
||||
continue
|
||||
|
||||
try:
|
||||
update_frontmatter(fp, classification)
|
||||
print(f"UPDATED: {filename} → {classification['category']} [{', '.join(classification['tags'])}]")
|
||||
updated += 1
|
||||
except Exception as e:
|
||||
print(f"ERROR: {filename}: {e}")
|
||||
errors += 1
|
||||
|
||||
print(f"\n{'='*50}")
|
||||
print(f"Total: {len(CLASSIFICATIONS)} | Updated: {updated} | Errors: {errors}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+26
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
slug="$1"
|
||||
url="$2"
|
||||
|
||||
echo "[$slug] Fetching $url" >&2
|
||||
html=$(curl -sL --max-time 15 "$url" 2>/dev/null) || { echo "[$slug] FETCH FAILED" >&2; echo ""; exit 0; }
|
||||
|
||||
# Try multiple og:image patterns
|
||||
img_url=$(echo "$html" | grep -oP 'property="og:image"\s+content="[^"]*"' | sed 's/.*content="//;s/"//' | head -1)
|
||||
|
||||
if [ -z "$img_url" ]; then
|
||||
img_url=$(echo "$html" | grep -oP 'name="og:image"\s+content="[^"]*"' | sed 's/.*content="//;s/"//' | head -1)
|
||||
fi
|
||||
|
||||
if [ -z "$img_url" ]; then
|
||||
img_url=$(echo "$html" | grep -oP 'content="[^"]*"[^>]*property="og:image"' | sed 's/.*content="//;s/".*//' | head -1)
|
||||
fi
|
||||
|
||||
if [ -n "$img_url" ]; then
|
||||
echo "[$slug] Found: $img_url" >&2
|
||||
echo "$img_url"
|
||||
else
|
||||
echo "[$slug] No og:image found" >&2
|
||||
fi
|
||||
@@ -0,0 +1,242 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Final attempt: search DDG text, scrape recipe pages for actual images."""
|
||||
|
||||
import csv
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from ddgs import DDGS
|
||||
|
||||
WORKDIR = Path("/home/jbrechtel/recipes/.worktrees/recipe-images")
|
||||
HEADERS = {
|
||||
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||
}
|
||||
SKIP_DOMAINS = {"pinterest", "facebook", "instagram", "twitter", "youtube", "tiktok"}
|
||||
MIN_IMAGE_SIZE = 10000 # 10KB minimum for a recipe photo
|
||||
|
||||
|
||||
def search_url(query: str) -> str | None:
|
||||
"""Search and return first relevant recipe page URL."""
|
||||
try:
|
||||
results = list(DDGS().text(query, max_results=8))
|
||||
except Exception as e:
|
||||
print(f" Search error: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
for r in results:
|
||||
url = r.get("href", "")
|
||||
domain = url.split("/")[2] if "//" in url else ""
|
||||
if any(s in domain for s in SKIP_DOMAINS):
|
||||
continue
|
||||
# Prefer recipe sites
|
||||
if any(kw in domain for kw in ["recipe", "cook", "food", "kitchen", "nytimes", "loveandlemons"]):
|
||||
return url
|
||||
|
||||
# Return first non-wikipedia result
|
||||
for r in results:
|
||||
url = r.get("href", "")
|
||||
if "wikipedia.org" not in url:
|
||||
return url
|
||||
return None
|
||||
|
||||
|
||||
def find_recipe_images(url: str) -> list[str]:
|
||||
"""Fetch a page and find actual recipe/food images."""
|
||||
try:
|
||||
resp = requests.get(url, headers=HEADERS, timeout=15, allow_redirects=True)
|
||||
resp.raise_for_status()
|
||||
except requests.RequestException:
|
||||
return []
|
||||
|
||||
soup = BeautifulSoup(resp.text, "html.parser")
|
||||
images = []
|
||||
|
||||
# 1. Check JSON-LD for recipe image
|
||||
for script in soup.find_all("script", type="application/ld+json"):
|
||||
try:
|
||||
data = json.loads(script.string)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
continue
|
||||
# Handle list or single
|
||||
items = data if isinstance(data, list) else [data]
|
||||
for item in items:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
if item.get("@type") == "Recipe":
|
||||
img = item.get("image")
|
||||
if isinstance(img, str):
|
||||
images.append(img)
|
||||
elif isinstance(img, list):
|
||||
for i in img:
|
||||
if isinstance(i, str):
|
||||
images.append(i)
|
||||
elif isinstance(i, dict):
|
||||
u = i.get("contentUrl") or i.get("url", "")
|
||||
if u:
|
||||
images.append(u)
|
||||
elif isinstance(img, dict):
|
||||
u = img.get("contentUrl") or img.get("url", "")
|
||||
if u:
|
||||
images.append(u)
|
||||
|
||||
# 2. Check og:image
|
||||
for meta in soup.find_all("meta"):
|
||||
content = meta.get("content", "")
|
||||
if (meta.get("property") == "og:image" or meta.get("name") == "og:image" or meta.get("name") == "twitter:image"):
|
||||
if content and "logo" not in content.lower():
|
||||
images.append(content)
|
||||
|
||||
# 3. Check all img tags for large, relevant images
|
||||
for img in soup.find_all("img"):
|
||||
src = img.get("src") or img.get("data-src") or ""
|
||||
if not src.startswith("http"):
|
||||
continue
|
||||
src_lower = src.lower()
|
||||
if any(kw in src_lower for kw in ["logo", "avatar", "icon", "banner", "pixel", "spacer", "thumb"]):
|
||||
continue
|
||||
# Check for recipe-related class/id
|
||||
parent_classes = ""
|
||||
parent = img.parent
|
||||
if parent:
|
||||
parent_classes = " ".join(parent.get("class", []) or []) + " " + (parent.get("id") or "")
|
||||
if any(kw in parent_classes.lower() for kw in ["logo", "nav", "header", "footer", "menu", "icon"]):
|
||||
continue
|
||||
images.append(src)
|
||||
|
||||
# Deduplicate
|
||||
seen = set()
|
||||
unique = []
|
||||
for img in images:
|
||||
if img not in seen:
|
||||
seen.add(img)
|
||||
unique.append(img)
|
||||
|
||||
return unique
|
||||
|
||||
|
||||
def download_image(url: str, dest: Path) -> bool:
|
||||
"""Download image, validate it's a real image."""
|
||||
try:
|
||||
resp = requests.get(url, headers=HEADERS, timeout=30, stream=True)
|
||||
resp.raise_for_status()
|
||||
ct = resp.headers.get("content-type", "")
|
||||
if not ct.startswith("image/"):
|
||||
return False
|
||||
content = resp.content
|
||||
if len(content) < MIN_IMAGE_SIZE:
|
||||
return False
|
||||
dest.write_bytes(content)
|
||||
return True
|
||||
except requests.RequestException:
|
||||
return False
|
||||
|
||||
|
||||
def get_title(filepath: Path) -> str:
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
m = re.search(r"^title:\s*(.+)$", content, re.MULTILINE)
|
||||
if m:
|
||||
return m.group(1).strip()
|
||||
return filepath.stem.replace("_", " ").replace("-", " ")
|
||||
|
||||
|
||||
def add_image_field(filepath: Path, slug: str):
|
||||
url = f"https://roux.roo.lol/recipe-images/{slug}.webp"
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
if re.search(r"^image:", content, re.MULTILINE):
|
||||
content = re.sub(r"^image:.*$", f"image: {url}", count=1, flags=re.MULTILINE, string=content)
|
||||
else:
|
||||
content = re.sub(r"^---$", f"image: {url}\n---", count=1, flags=re.MULTILINE, string=content)
|
||||
filepath.write_text(content, encoding="utf-8")
|
||||
print(f" Updated {filepath.name}", file=sys.stderr)
|
||||
|
||||
|
||||
def get_source_url(filepath: Path) -> str | None:
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
m = re.search(r"^source:\s*(.+)$", content, re.MULTILINE)
|
||||
if m:
|
||||
val = m.group(1).strip()
|
||||
if val.startswith("http"):
|
||||
return val
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
csv_file = WORKDIR / "docs" / "no-image-has-fm.csv"
|
||||
|
||||
recipes = []
|
||||
with open(csv_file) as f:
|
||||
reader = csv.reader(f, delimiter="|")
|
||||
for row in reader:
|
||||
if len(row) >= 2:
|
||||
recipes.append((row[0].strip(), row[1].strip()))
|
||||
|
||||
success = []
|
||||
failed = []
|
||||
|
||||
for filename, slug in recipes:
|
||||
filepath = WORKDIR / filename
|
||||
if not filepath.exists():
|
||||
failed.append(slug)
|
||||
continue
|
||||
|
||||
dest = WORKDIR / "recipe-images" / f"{slug}.webp"
|
||||
|
||||
# Skip if already has image AND file exists
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
has_field = bool(re.search(r"^image:", content, re.MULTILINE))
|
||||
has_file = dest.exists()
|
||||
if has_field and has_file and dest.stat().st_size > 10000:
|
||||
print(f"[{slug}] Already done, skipping", file=sys.stderr)
|
||||
success.append(slug)
|
||||
continue
|
||||
|
||||
title = get_title(filepath)
|
||||
query = f"{title} recipe"
|
||||
|
||||
print(f"[{slug}] Searching: {query}", file=sys.stderr)
|
||||
page_url = search_url(query)
|
||||
if not page_url:
|
||||
print(f" No search results", file=sys.stderr)
|
||||
failed.append(slug)
|
||||
continue
|
||||
|
||||
print(f" Page: {page_url}", file=sys.stderr)
|
||||
time.sleep(0.3)
|
||||
|
||||
img_urls = find_recipe_images(page_url)
|
||||
if not img_urls:
|
||||
print(f" No images found on page", file=sys.stderr)
|
||||
failed.append(slug)
|
||||
continue
|
||||
|
||||
downloaded = False
|
||||
for img_url in img_urls[:5]:
|
||||
print(f" Trying: {img_url[:80]}...", file=sys.stderr)
|
||||
if download_image(img_url, dest):
|
||||
print(f" Downloaded ({dest.stat().st_size} bytes)", file=sys.stderr)
|
||||
add_image_field(filepath, slug)
|
||||
downloaded = True
|
||||
break
|
||||
time.sleep(0.2)
|
||||
|
||||
if downloaded:
|
||||
success.append(slug)
|
||||
else:
|
||||
failed.append(slug)
|
||||
|
||||
time.sleep(0.5)
|
||||
|
||||
print(f"\n=== Results ===", file=sys.stderr)
|
||||
print(f"Success: {len(success)}", file=sys.stderr)
|
||||
print(f"Failed: {len(failed)}", file=sys.stderr)
|
||||
if failed:
|
||||
print(f"Failed: {', '.join(failed)}", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,95 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Find images for remaining 6 recipes."""
|
||||
import re, sys, time
|
||||
from pathlib import Path
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from ddgs import DDGS
|
||||
|
||||
WORKDIR = Path("/home/jbrechtel/recipes/.worktrees/recipe-images")
|
||||
HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"}
|
||||
|
||||
recipes = [
|
||||
("Arugula Pesto Chicken with Pecans & Roasted Butternut.cook", "arugula-pesto-chicken-with-pecans-roasted-butternut", "Arugula Pesto Chicken Pecans Roasted Butternut recipe garnishandgather"),
|
||||
("Bang Bang Tofu.cook", "bang-bang-tofu", "Bang Bang Tofu recipe"),
|
||||
("Chana Chickpea Salad.cook", "chana-chickpea-salad", "Chana Chickpea Salad recipe"),
|
||||
("Gaja's Bulgogi Lettuce Wraps.cook", "gaja-s-bulgogi-lettuce-wraps", "Gaja Bulgogi Lettuce Wraps recipe garnishandgather"),
|
||||
("Pecan-Brown Butter Trout.cook", "pecan-brown-butter-trout", "Pecan Brown Butter Trout recipe"),
|
||||
("West African Peanut Chicken.cook", "west-african-peanut-chicken", "West African Peanut Chicken recipe"),
|
||||
]
|
||||
|
||||
for file, slug, query in recipes:
|
||||
fp = WORKDIR / file
|
||||
dest = WORKDIR / "recipe-images" / f"{slug}.webp"
|
||||
if dest.exists() and dest.stat().st_size > 10000:
|
||||
print(f"[{slug}] Already done")
|
||||
continue
|
||||
|
||||
print(f"[{slug}] Searching: {query}")
|
||||
try:
|
||||
results = list(DDGS().text(query, max_results=5))
|
||||
except Exception as e:
|
||||
print(f" Search error: {e}")
|
||||
continue
|
||||
|
||||
found = False
|
||||
for r in results:
|
||||
url = r.get("href", "")
|
||||
if not url or any(s in url for s in ["pinterest","facebook","instagram"]):
|
||||
continue
|
||||
print(f" Fetching: {url}")
|
||||
try:
|
||||
resp = requests.get(url, headers=HEADERS, timeout=15)
|
||||
resp.raise_for_status()
|
||||
except Exception as e:
|
||||
print(f" Failed: {e}")
|
||||
continue
|
||||
|
||||
soup = BeautifulSoup(resp.text, "html.parser")
|
||||
img_urls = []
|
||||
|
||||
# og:image
|
||||
for meta in soup.find_all("meta"):
|
||||
c = meta.get("content", "")
|
||||
p = (meta.get("property") or "").lower()
|
||||
n = (meta.get("name") or "").lower()
|
||||
if ("og:image" in p or "og:image" in n or "twitter:image" in n) and c and "logo" not in c.lower():
|
||||
img_urls.append(c)
|
||||
|
||||
# img tags
|
||||
for img in soup.find_all("img"):
|
||||
src = img.get("src") or img.get("data-src") or ""
|
||||
if src.startswith("http") and "logo" not in src.lower() and "icon" not in src.lower():
|
||||
img_urls.append(src)
|
||||
|
||||
for img_url in img_urls[:5]:
|
||||
try:
|
||||
r2 = requests.get(img_url, headers=HEADERS, timeout=15)
|
||||
r2.raise_for_status()
|
||||
ct = r2.headers.get("content-type", "")
|
||||
if ct.startswith("image/") and len(r2.content) > 10000:
|
||||
dest.write_bytes(r2.content)
|
||||
print(f" Downloaded ({len(r2.content)} bytes): {img_url[:60]}")
|
||||
found = True
|
||||
break
|
||||
except:
|
||||
pass
|
||||
if found:
|
||||
break
|
||||
time.sleep(0.3)
|
||||
|
||||
if found:
|
||||
# Add image field
|
||||
url_val = f"https://roux.roo.lol/recipe-images/{slug}.webp"
|
||||
content = fp.read_text(encoding="utf-8")
|
||||
if re.search(r"^image:", content, re.MULTILINE):
|
||||
content = re.sub(r"^image:.*$", f"image: {url_val}", count=1, flags=re.MULTILINE, string=content)
|
||||
else:
|
||||
content = re.sub(r"^---$", f"image: {url_val}\n---", count=1, flags=re.MULTILINE, string=content)
|
||||
fp.write_text(content, encoding="utf-8")
|
||||
print(f" Updated image field")
|
||||
else:
|
||||
print(f" FAILED")
|
||||
time.sleep(0.5)
|
||||
|
||||
print("\nDone!")
|
||||
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fix the broken YAML frontmatter: move image field inside YAML, and convert non-WebP images."""
|
||||
|
||||
import re, subprocess, sys
|
||||
from pathlib import Path
|
||||
|
||||
WORKDIR = Path("/home/jbrechtel/recipes/.worktrees/recipe-images")
|
||||
|
||||
def fix_frontmatter(content: str) -> str:
|
||||
"""Move image: field inside the YAML block if it's outside."""
|
||||
# Check if image: is before the opening ---
|
||||
lines = content.split('\n')
|
||||
|
||||
# Find the first --- line
|
||||
first_hr = -1
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip() == '---':
|
||||
first_hr = i
|
||||
break
|
||||
|
||||
if first_hr <= 0:
|
||||
return content # No frontmatter or image is at first line
|
||||
|
||||
# Check if the line just before the first --- has image:
|
||||
if first_hr > 0 and lines[first_hr - 1].startswith('image:'):
|
||||
# Extract the image line
|
||||
img_line = lines[first_hr - 1]
|
||||
# Remove it from before ---
|
||||
del lines[first_hr - 1]
|
||||
# Now first_hr is one index less (or same if we deleted the line before)
|
||||
# Find the closing ---
|
||||
closing_hr = -1
|
||||
for i in range(first_hr, len(lines)):
|
||||
if lines[i].strip() == '---':
|
||||
closing_hr = i
|
||||
break
|
||||
|
||||
if closing_hr > first_hr:
|
||||
# Insert image line before the closing ---
|
||||
lines.insert(closing_hr, img_line)
|
||||
else:
|
||||
# Insert after the closing --- (fallback)
|
||||
lines.insert(first_hr + 1, img_line)
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
|
||||
def fix_all_frontmatter():
|
||||
"""Fix frontmatter for all .cook files."""
|
||||
fixed = 0
|
||||
for f in sorted(WORKDIR.glob("*.cook")):
|
||||
content = f.read_text(encoding="utf-8")
|
||||
fixed_content = fix_frontmatter(content)
|
||||
if fixed_content != content:
|
||||
f.write_text(fixed_content, encoding="utf-8")
|
||||
print(f" Fixed: {f.name}")
|
||||
fixed += 1
|
||||
print(f"\nFixed {fixed} files with mis-placed image fields")
|
||||
return fixed
|
||||
|
||||
|
||||
def convert_images():
|
||||
"""Convert non-WebP images to actual WebP format."""
|
||||
converted = 0
|
||||
for img in sorted(WORKDIR.glob("recipe-images/*.webp")):
|
||||
mime = subprocess.run(
|
||||
["file", "-b", "--mime-type", str(img)],
|
||||
capture_output=True, text=True
|
||||
).stdout.strip()
|
||||
|
||||
if mime != "image/webp":
|
||||
print(f" Converting {img.name} (mime: {mime})")
|
||||
# Use temp file to avoid corruption
|
||||
tmp = img.with_suffix(".webp.tmp")
|
||||
result = subprocess.run(
|
||||
["convert", str(img), str(tmp)],
|
||||
capture_output=True, text=True
|
||||
)
|
||||
if result.returncode == 0 and tmp.exists():
|
||||
tmp.replace(img)
|
||||
print(f" Converted successfully")
|
||||
converted += 1
|
||||
else:
|
||||
print(f" Convert failed: {result.stderr}")
|
||||
|
||||
print(f"\nConverted {converted} images to proper WebP")
|
||||
return converted
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=== Fixing YAML frontmatter ===")
|
||||
fix_all_frontmatter()
|
||||
|
||||
print("\n=== Converting image formats ===")
|
||||
convert_images()
|
||||
|
||||
# Final check
|
||||
print("\n=== Final verification ===")
|
||||
bad = 0
|
||||
for f in sorted(WORKDIR.glob("*.cook")):
|
||||
content = f.read_text(encoding="utf-8")
|
||||
# Check if image: is between --- markers
|
||||
if "image:" in content:
|
||||
# Find the opening ---
|
||||
parts = content.split('---', 2)
|
||||
if len(parts) >= 3:
|
||||
if "image:" not in parts[1]:
|
||||
print(f" STILL BROKEN: {f.name}")
|
||||
bad += 1
|
||||
|
||||
if bad == 0:
|
||||
print(" All files have image: inside YAML block ✅")
|
||||
|
||||
webp_count = 0
|
||||
non_webp_count = 0
|
||||
for img in sorted(WORKDIR.glob("recipe-images/*.webp")):
|
||||
mime = subprocess.run(
|
||||
["file", "-b", "--mime-type", str(img)],
|
||||
capture_output=True, text=True
|
||||
).stdout.strip()
|
||||
if mime == "image/webp":
|
||||
webp_count += 1
|
||||
else:
|
||||
non_webp_count += 1
|
||||
if non_webp_count <= 5:
|
||||
print(f" NOT WEBP: {img.name} ({mime})")
|
||||
|
||||
print(f" WebP images: {webp_count}, Non-WebP: {non_webp_count}")
|
||||
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Scrape og:image from recipe source URLs, download images, update .cook files."""
|
||||
|
||||
import csv
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
WORKDIR = Path("/home/jbrechtel/recipes/.worktrees/recipe-images")
|
||||
HEADERS = {
|
||||
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
||||
}
|
||||
|
||||
|
||||
def get_source_url(filepath: Path) -> str | None:
|
||||
"""Extract the source URL from a .cook file's YAML frontmatter."""
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
match = re.search(r"^source:\s*(.+)$", content, re.MULTILINE)
|
||||
if not match:
|
||||
return None
|
||||
val = match.group(1).strip()
|
||||
# Only return actual URLs
|
||||
if val.startswith("http://") or val.startswith("https://"):
|
||||
return val
|
||||
return None
|
||||
|
||||
|
||||
def fetch_og_image(url: str, timeout: int = 20) -> str | None:
|
||||
"""Fetch a page and extract og:image URL."""
|
||||
try:
|
||||
resp = requests.get(url, headers=HEADERS, timeout=timeout, allow_redirects=True)
|
||||
resp.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
print(f" FETCH FAILED: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
soup = BeautifulSoup(resp.text, "html.parser")
|
||||
|
||||
# Try og:image meta tag
|
||||
for meta in soup.find_all("meta"):
|
||||
if meta.get("property") == "og:image" and meta.get("content"):
|
||||
return meta["content"]
|
||||
if meta.get("name") == "og:image" and meta.get("content"):
|
||||
return meta["content"]
|
||||
|
||||
# Try twitter:image
|
||||
for meta in soup.find_all("meta"):
|
||||
if meta.get("name") == "twitter:image" and meta.get("content"):
|
||||
return meta["content"]
|
||||
|
||||
# Try finding the first large image (common in recipe sites)
|
||||
for img in soup.find_all("img"):
|
||||
src = img.get("src") or ""
|
||||
if "logo" in src.lower() or "avatar" in src.lower() or "icon" in src.lower():
|
||||
continue
|
||||
if src.startswith("http") and any(kw in src.lower() for kw in ["upload", "wp-content", "images"]):
|
||||
return src
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def download_image(url: str, dest: Path) -> bool:
|
||||
"""Download an image from URL to destination path."""
|
||||
try:
|
||||
resp = requests.get(url, headers=HEADERS, timeout=30, stream=True)
|
||||
resp.raise_for_status()
|
||||
dest.write_bytes(resp.content)
|
||||
return True
|
||||
except requests.RequestException as e:
|
||||
print(f" DOWNLOAD FAILED: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def add_image_field(filepath: Path, slug: str):
|
||||
"""Add or update the image field in a .cook file's YAML frontmatter."""
|
||||
url = f"https://roux.roo.lol/recipe-images/{slug}.webp"
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
|
||||
# If image field already exists, replace it
|
||||
if re.search(r"^image:", content, re.MULTILINE):
|
||||
content = re.sub(r"^image:.*$", f"image: {url}", content, count=1, flags=re.MULTILINE)
|
||||
else:
|
||||
# Insert before closing ---
|
||||
content = re.sub(r"^---$", f"image: {url}\n---", content, count=1, flags=re.MULTILINE)
|
||||
|
||||
filepath.write_text(content, encoding="utf-8")
|
||||
print(f" Updated image field: {filepath.name}", file=sys.stderr)
|
||||
|
||||
|
||||
def process_recipe(filepath: Path, slug: str) -> bool:
|
||||
"""Process a single recipe: find image, download, update frontmatter."""
|
||||
dest = WORKDIR / "recipe-images" / f"{slug}.webp"
|
||||
|
||||
# Skip if already downloaded
|
||||
if dest.exists():
|
||||
print(f"[{slug}] Image already exists, skipping download", file=sys.stderr)
|
||||
add_image_field(filepath, slug)
|
||||
return True
|
||||
|
||||
# Try to get source URL and scrape image
|
||||
src_url = get_source_url(filepath)
|
||||
if src_url:
|
||||
print(f"[{slug}] Scraping {src_url}", file=sys.stderr)
|
||||
img_url = fetch_og_image(src_url)
|
||||
if img_url:
|
||||
print(f" Found: {img_url}", file=sys.stderr)
|
||||
if download_image(img_url, dest):
|
||||
print(f" Downloaded to {dest.name}", file=sys.stderr)
|
||||
add_image_field(filepath, slug)
|
||||
return True
|
||||
else:
|
||||
print(f" Download failed", file=sys.stderr)
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
csv_file = WORKDIR / "docs" / "no-image-has-fm.csv"
|
||||
if not csv_file.exists():
|
||||
print(f"CSV not found: {csv_file}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
recipes = []
|
||||
with open(csv_file) as f:
|
||||
reader = csv.reader(f, delimiter="|")
|
||||
for row in reader:
|
||||
if len(row) >= 2:
|
||||
recipes.append((row[0].strip(), row[1].strip()))
|
||||
|
||||
print(f"Processing {len(recipes)} recipes...", file=sys.stderr)
|
||||
|
||||
success = []
|
||||
failed = []
|
||||
|
||||
for filename, slug in recipes:
|
||||
filepath = WORKDIR / filename
|
||||
if not filepath.exists():
|
||||
print(f"[{slug}] File not found: {filepath}", file=sys.stderr)
|
||||
failed.append(slug)
|
||||
continue
|
||||
|
||||
ok = process_recipe(filepath, slug)
|
||||
if ok:
|
||||
success.append(slug)
|
||||
else:
|
||||
failed.append(slug)
|
||||
|
||||
# Be polite to servers
|
||||
time.sleep(0.5)
|
||||
|
||||
print(f"\n=== Results ===", file=sys.stderr)
|
||||
print(f"Success: {len(success)}", file=sys.stderr)
|
||||
print(f"Failed: {len(failed)}", file=sys.stderr)
|
||||
if failed:
|
||||
print(f"Failed slugs: {', '.join(failed)}", file=sys.stderr)
|
||||
|
||||
# Write failed slugs to file for retry
|
||||
if failed:
|
||||
with open(WORKDIR / "docs" / "failed-images.csv", "w") as f:
|
||||
for slug in failed:
|
||||
f.write(f"{slug}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Search the web for recipe images, download them, and update .cook files.
|
||||
|
||||
Uses DuckDuckGo search to find recipe pages, then scrapes og:image.
|
||||
"""
|
||||
|
||||
import csv
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from duckduckgo_search import DDGS
|
||||
|
||||
WORKDIR = Path("/home/jbrechtel/recipes/.worktrees/recipe-images")
|
||||
HEADERS = {
|
||||
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
|
||||
}
|
||||
|
||||
IMAGE_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp", ".gif"}
|
||||
|
||||
|
||||
def fetch_og_image(url: str) -> str | None:
|
||||
"""Fetch a page and extract og:image URL."""
|
||||
try:
|
||||
resp = requests.get(url, headers=HEADERS, timeout=15, allow_redirects=True)
|
||||
resp.raise_for_status()
|
||||
except requests.RequestException:
|
||||
return None
|
||||
|
||||
soup = BeautifulSoup(resp.text, "html.parser")
|
||||
|
||||
# Try og:image meta tag (property or name)
|
||||
for meta in soup.find_all("meta"):
|
||||
prop = (meta.get("property") or "").lower()
|
||||
name = (meta.get("name") or "").lower()
|
||||
content = meta.get("content", "")
|
||||
if "og:image" in prop and content:
|
||||
return content
|
||||
if "og:image" in name and content:
|
||||
return content
|
||||
if "twitter:image" in name and content:
|
||||
return content
|
||||
|
||||
# Try schema.org JSON-LD
|
||||
for script in soup.find_all("script", type="application/ld+json"):
|
||||
import json
|
||||
try:
|
||||
data = json.loads(script.string)
|
||||
if isinstance(data, dict):
|
||||
img = data.get("image")
|
||||
if isinstance(img, str) and img.startswith("http"):
|
||||
return img
|
||||
if isinstance(img, dict):
|
||||
url = img.get("contentUrl") or img.get("url", "")
|
||||
if url.startswith("http"):
|
||||
return url
|
||||
if isinstance(img, list) and img:
|
||||
first = img[0]
|
||||
if isinstance(first, str):
|
||||
return first
|
||||
if isinstance(first, dict):
|
||||
url = first.get("contentUrl") or first.get("url", "")
|
||||
if url.startswith("http"):
|
||||
return url
|
||||
except (json.JSONDecodeError, AttributeError):
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def search_and_find_image(query: str) -> str | None:
|
||||
"""Search DuckDuckGo for a recipe and find og:image."""
|
||||
try:
|
||||
results = list(DDGS().text(query, max_results=5))
|
||||
except Exception as e:
|
||||
print(f" Search error: {e}", file=sys.stderr)
|
||||
return None
|
||||
|
||||
for r in results:
|
||||
url = r.get("href", "")
|
||||
if not url or "pinterest" in url or "facebook" in url or "instagram" in url:
|
||||
continue
|
||||
print(f" Trying: {url}", file=sys.stderr)
|
||||
img = fetch_og_image(url)
|
||||
if img:
|
||||
print(f" Found image: {img}", file=sys.stderr)
|
||||
return img
|
||||
time.sleep(0.3)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def download_image(url: str, dest: Path) -> bool:
|
||||
"""Download an image from URL to destination path."""
|
||||
try:
|
||||
resp = requests.get(url, headers=HEADERS, timeout=30, stream=True)
|
||||
resp.raise_for_status()
|
||||
dest.write_bytes(resp.content)
|
||||
return True
|
||||
except requests.RequestException as e:
|
||||
print(f" Download failed: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def add_image_field(filepath: Path, slug: str):
|
||||
"""Add or update the image field in a .cook file's YAML frontmatter."""
|
||||
url = f"https://roux.roo.lol/recipe-images/{slug}.webp"
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
|
||||
if re.search(r"^image:", content, re.MULTILINE):
|
||||
content = re.sub(r"^image:.*$", f"image: {url}", count=1, flags=re.MULTILINE, string=content)
|
||||
else:
|
||||
content = re.sub(r"^---$", f"image: {url}\n---", count=1, flags=re.MULTILINE, string=content)
|
||||
|
||||
filepath.write_text(content, encoding="utf-8")
|
||||
print(f" Updated {filepath.name}", file=sys.stderr)
|
||||
|
||||
|
||||
def get_source_url(filepath: Path) -> str | None:
|
||||
"""Extract source URL from .cook frontmatter."""
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
match = re.search(r"^source:\s*(.+)$", content, re.MULTILINE)
|
||||
if match:
|
||||
val = match.group(1).strip()
|
||||
if val.startswith("http://") or val.startswith("https://"):
|
||||
return val
|
||||
return None
|
||||
|
||||
|
||||
def process_recipe(filename: str, slug: str) -> bool:
|
||||
"""Try to find and download an image for a recipe."""
|
||||
filepath = WORKDIR / filename
|
||||
dest = WORKDIR / "recipe-images" / f"{slug}.webp"
|
||||
|
||||
if dest.exists():
|
||||
print(f"[{slug}] Already exists", file=sys.stderr)
|
||||
add_image_field(filepath, slug)
|
||||
return True
|
||||
|
||||
# Strategy 1: Try source URL if available
|
||||
src_url = get_source_url(filepath)
|
||||
if src_url:
|
||||
print(f"[{slug}] Trying source URL: {src_url}", file=sys.stderr)
|
||||
img = fetch_og_image(src_url)
|
||||
if img:
|
||||
if download_image(img, dest):
|
||||
print(f" Downloaded from source", file=sys.stderr)
|
||||
add_image_field(filepath, slug)
|
||||
return True
|
||||
|
||||
# Strategy 2: Search by recipe name
|
||||
# Get the title from frontmatter
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
title_match = re.search(r"^title:\s*(.+)$", content, re.MULTILINE)
|
||||
if title_match:
|
||||
query = title_match.group(1).strip() + " recipe"
|
||||
else:
|
||||
query = filename.replace(".cook", "").replace("_", " ").replace("-", " ") + " recipe"
|
||||
|
||||
print(f"[{slug}] Searching: {query}", file=sys.stderr)
|
||||
img = search_and_find_image(query)
|
||||
if img and download_image(img, dest):
|
||||
print(f" Downloaded from search", file=sys.stderr)
|
||||
add_image_field(filepath, slug)
|
||||
return True
|
||||
|
||||
# Strategy 3: Try with "og:image" or "recipe image" variations
|
||||
query2 = query + " image"
|
||||
print(f"[{slug}] Searching (alt): {query2}", file=sys.stderr)
|
||||
img = search_and_find_image(query2)
|
||||
if img and download_image(img, dest):
|
||||
print(f" Downloaded from alt search", file=sys.stderr)
|
||||
add_image_field(filepath, slug)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
# First process the no-image-has-fm list
|
||||
csv_file = WORKDIR / "docs" / "no-image-has-fm.csv"
|
||||
if not csv_file.exists():
|
||||
print(f"CSV not found: {csv_file}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
recipes = []
|
||||
with open(csv_file) as f:
|
||||
reader = csv.reader(f, delimiter="|")
|
||||
for row in reader:
|
||||
if len(row) >= 2:
|
||||
recipes.append((row[0].strip(), row[1].strip()))
|
||||
|
||||
success = []
|
||||
failed = []
|
||||
|
||||
for filename, slug in recipes:
|
||||
filepath = WORKDIR / filename
|
||||
if not filepath.exists():
|
||||
failed.append(slug)
|
||||
continue
|
||||
|
||||
dest = WORKDIR / "recipe-images" / f"{slug}.webp"
|
||||
if dest.exists() and re.search(r"^image:", filepath.read_text(), re.MULTILINE):
|
||||
print(f"[{slug}] Already processed, skipping", file=sys.stderr)
|
||||
success.append(slug)
|
||||
continue
|
||||
|
||||
ok = process_recipe(filename, slug)
|
||||
if ok:
|
||||
success.append(slug)
|
||||
else:
|
||||
failed.append(slug)
|
||||
|
||||
# Be polite
|
||||
time.sleep(0.5)
|
||||
|
||||
print(f"\n=== Results ===", file=sys.stderr)
|
||||
print(f"Success: {len(success)}", file=sys.stderr)
|
||||
print(f"Failed: {len(failed)}", file=sys.stderr)
|
||||
|
||||
if failed:
|
||||
with open(WORKDIR / "docs" / "failed-images.csv", "w") as f:
|
||||
for s in failed:
|
||||
f.write(f"{s}\n")
|
||||
print(f"Failed: {', '.join(failed)}", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Search DuckDuckGo Images for recipe photos, download, update .cook files."""
|
||||
|
||||
import csv
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from duckduckgo_search import DDGS
|
||||
|
||||
WORKDIR = Path("/home/jbrechtel/recipes/.worktrees/recipe-images")
|
||||
HEADERS = {
|
||||
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
|
||||
}
|
||||
|
||||
|
||||
def download_image(url: str, dest: Path) -> bool:
|
||||
"""Download an image from URL to destination path."""
|
||||
try:
|
||||
resp = requests.get(url, headers=HEADERS, timeout=30, stream=True)
|
||||
resp.raise_for_status()
|
||||
# Validate it's actually an image
|
||||
content_type = resp.headers.get("content-type", "")
|
||||
if not content_type.startswith("image/"):
|
||||
print(f" Not an image (content-type: {content_type})", file=sys.stderr)
|
||||
return False
|
||||
dest.write_bytes(resp.content)
|
||||
return True
|
||||
except requests.RequestException as e:
|
||||
print(f" Download failed: {e}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def add_image_field(filepath: Path, slug: str):
|
||||
"""Add or update the image field in a .cook file."""
|
||||
url = f"https://roux.roo.lol/recipe-images/{slug}.webp"
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
|
||||
if re.search(r"^image:", content, re.MULTILINE):
|
||||
content = re.sub(r"^image:.*$", f"image: {url}", count=1, flags=re.MULTILINE, string=content)
|
||||
else:
|
||||
content = re.sub(r"^---$", f"image: {url}\n---", count=1, flags=re.MULTILINE, string=content)
|
||||
|
||||
filepath.write_text(content, encoding="utf-8")
|
||||
print(f" Updated {filepath.name}", file=sys.stderr)
|
||||
|
||||
|
||||
def search_images(query: str, max_results: int = 5):
|
||||
"""Search DuckDuckGo Images for a recipe and return image URLs."""
|
||||
try:
|
||||
results = list(DDGS().images(query, max_results=max_results))
|
||||
return [r["image"] for r in results if r.get("image")]
|
||||
except Exception as e:
|
||||
print(f" Image search error: {e}", file=sys.stderr)
|
||||
return []
|
||||
|
||||
|
||||
def get_title(filepath: Path) -> str:
|
||||
"""Extract title from .cook frontmatter."""
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
match = re.search(r"^title:\s*(.+)$", content, re.MULTILINE)
|
||||
if match:
|
||||
return match.group(1).strip()
|
||||
# Fall back to filename
|
||||
name = filepath.stem
|
||||
return name.replace("_", " ").replace("-", " ")
|
||||
|
||||
|
||||
def main():
|
||||
csv_file = WORKDIR / "docs" / "failed-images.csv"
|
||||
# Also check which are actually missing
|
||||
existing_csv = WORKDIR / "docs" / "no-image-has-fm.csv"
|
||||
|
||||
recipes = []
|
||||
with open(existing_csv) as f:
|
||||
reader = csv.reader(f, delimiter="|")
|
||||
for row in reader:
|
||||
if len(row) >= 2:
|
||||
recipes.append((row[0].strip(), row[1].strip()))
|
||||
|
||||
# Filter to those without an image file or without image field
|
||||
to_process = []
|
||||
for filename, slug in recipes:
|
||||
filepath = WORKDIR / filename
|
||||
dest = WORKDIR / "recipe-images" / f"{slug}.webp"
|
||||
has_image_file = dest.exists()
|
||||
has_image_field = False
|
||||
if filepath.exists():
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
has_image_field = bool(re.search(r"^image:", content, re.MULTILINE))
|
||||
|
||||
if not has_image_file or not has_image_field:
|
||||
to_process.append((filename, slug))
|
||||
|
||||
print(f"Processing {len(to_process)} remaining recipes...", file=sys.stderr)
|
||||
|
||||
success = []
|
||||
failed = []
|
||||
|
||||
for filename, slug in to_process:
|
||||
filepath = WORKDIR / filename
|
||||
dest = WORKDIR / "recipe-images" / f"{slug}.webp"
|
||||
|
||||
if not filepath.exists():
|
||||
failed.append(slug)
|
||||
continue
|
||||
|
||||
title = get_title(filepath)
|
||||
queries = [
|
||||
f"{title} recipe",
|
||||
title,
|
||||
]
|
||||
|
||||
found = False
|
||||
for query in queries:
|
||||
print(f"[{slug}] Searching images: {query}", file=sys.stderr)
|
||||
urls = search_images(query)
|
||||
for img_url in urls:
|
||||
# Skip tiny icons, logos
|
||||
if any(kw in img_url.lower() for kw in ["logo", "icon", "avatar", "merriam", "wikipedia"]):
|
||||
continue
|
||||
print(f" Trying: {img_url[:80]}...", file=sys.stderr)
|
||||
if download_image(img_url, dest):
|
||||
print(f" Downloaded!", file=sys.stderr)
|
||||
add_image_field(filepath, slug)
|
||||
found = True
|
||||
break
|
||||
time.sleep(0.2)
|
||||
if found:
|
||||
break
|
||||
time.sleep(0.5)
|
||||
|
||||
if found:
|
||||
success.append(slug)
|
||||
else:
|
||||
failed.append(slug)
|
||||
|
||||
print(f"\n=== Results ===", file=sys.stderr)
|
||||
print(f"Success: {len(success)}", file=sys.stderr)
|
||||
print(f"Failed: {len(failed)}", file=sys.stderr)
|
||||
if failed:
|
||||
print(f"Failed: {', '.join(failed)}", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Add frontmatter and images to 9 recipes without frontmatter."""
|
||||
|
||||
import re, sys, time
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from ddgs import DDGS
|
||||
|
||||
WORKDIR = Path("/home/jbrechtel/recipes/.worktrees/recipe-images")
|
||||
HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"}
|
||||
|
||||
# (filename, slug, title, source_url_or_empty)
|
||||
recipes = [
|
||||
("Black Pepper Tofu (Ottolenghi).cook", "black-pepper-tofu-ottolenghi", "Black Pepper Tofu (Ottolenghi)", ""),
|
||||
("Black Pepper Tofu with Bok Choy.cook", "black-pepper-tofu-with-bok-choy", "Black Pepper Tofu with Bok Choy", ""),
|
||||
("butternut-turkey-chili.cook", "butternut-turkey-chili", "Butternut & Turkey Chili", ""),
|
||||
("Caramel Cake.cook", "caramel-cake", "Caramel Cake", ""),
|
||||
("Caramel Icing.cook", "caramel-icing", "Caramel Icing", ""),
|
||||
("example-fried-rice.cook", "example-fried-rice", "Fried Rice", ""),
|
||||
("indian-spiced-chicken.cook", "indian-spiced-chicken", "Indian Spiced Chicken", ""),
|
||||
("Italian Sausage and Delicata Rigatoni.cook", "italian-sausage-and-delicata-rigatoni", "Italian Sausage & Delicata Rigatoni", ""),
|
||||
("shrimp-feta.cook", "shrimp-feta", "Orzo with shrimp, tomato and marinated feta", "https://thehappyfoodie.co.uk/recipes/ottolenghis-orzo-with-prawns-tomato-and-marinated-feta/"),
|
||||
]
|
||||
|
||||
|
||||
def fetch_og_image(url: str) -> str | None:
|
||||
try:
|
||||
resp = requests.get(url, headers=HEADERS, timeout=15)
|
||||
resp.raise_for_status()
|
||||
except:
|
||||
return None
|
||||
soup = BeautifulSoup(resp.text, "html.parser")
|
||||
for meta in soup.find_all("meta"):
|
||||
c = meta.get("content", "")
|
||||
p = (meta.get("property") or "").lower()
|
||||
n = (meta.get("name") or "").lower()
|
||||
if ("og:image" in p or "og:image" in n or "twitter:image" in n) and c:
|
||||
return c
|
||||
# JSON-LD
|
||||
for script in soup.find_all("script", type="application/ld+json"):
|
||||
try:
|
||||
import json
|
||||
data = json.loads(script.string)
|
||||
items = data if isinstance(data, list) else [data]
|
||||
for item in items:
|
||||
if isinstance(item, dict) and item.get("@type") == "Recipe":
|
||||
img = item.get("image")
|
||||
if isinstance(img, str): return img
|
||||
if isinstance(img, list):
|
||||
for i in img:
|
||||
if isinstance(i, str): return i
|
||||
if isinstance(i, dict):
|
||||
u = i.get("contentUrl") or i.get("url", "")
|
||||
if u: return u
|
||||
if isinstance(img, dict):
|
||||
u = img.get("contentUrl") or img.get("url", "")
|
||||
if u: return u
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def search_and_scrape(query: str) -> str | None:
|
||||
try:
|
||||
results = list(DDGS().text(query, max_results=5))
|
||||
except:
|
||||
return None
|
||||
for r in results:
|
||||
url = r.get("href", "")
|
||||
if not url or "pinterest" in url or "facebook" in url:
|
||||
continue
|
||||
img = fetch_og_image(url)
|
||||
if img:
|
||||
return img
|
||||
time.sleep(0.3)
|
||||
return None
|
||||
|
||||
|
||||
def download_image(url: str, dest: Path) -> bool:
|
||||
try:
|
||||
resp = requests.get(url, headers=HEADERS, timeout=30)
|
||||
resp.raise_for_status()
|
||||
ct = resp.headers.get("content-type", "")
|
||||
if not ct.startswith("image/"):
|
||||
return False
|
||||
content = resp.content
|
||||
if len(content) < 5000:
|
||||
return False
|
||||
dest.write_bytes(content)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
def add_frontmatter(filepath: Path, slug: str, title: str):
|
||||
url = f"https://roux.roo.lol/recipe-images/{slug}.webp"
|
||||
content = filepath.read_text(encoding="utf-8")
|
||||
|
||||
frontmatter = f"""---
|
||||
title: {title}
|
||||
description:
|
||||
servings: 1
|
||||
category: uncategorized
|
||||
source: Unknown
|
||||
image: {url}
|
||||
---
|
||||
|
||||
"""
|
||||
filepath.write_text(frontmatter + content, encoding="utf-8")
|
||||
print(f" Added frontmatter to {filepath.name}", file=sys.stderr)
|
||||
|
||||
|
||||
def main():
|
||||
for filename, slug, title, src_url in recipes:
|
||||
fp = WORKDIR / filename
|
||||
dest = WORKDIR / "recipe-images" / f"{slug}.webp"
|
||||
|
||||
print(f"[{slug}] Processing {filename}", file=sys.stderr)
|
||||
|
||||
# Find and download image
|
||||
img_url = None
|
||||
if src_url:
|
||||
print(f" Trying source URL: {src_url}", file=sys.stderr)
|
||||
img_url = fetch_og_image(src_url)
|
||||
time.sleep(0.3)
|
||||
|
||||
if not img_url:
|
||||
print(f" Searching: {title} recipe", file=sys.stderr)
|
||||
img_url = search_and_scrape(f"{title} recipe")
|
||||
time.sleep(0.3)
|
||||
|
||||
if not img_url:
|
||||
print(f" Searching (alt): {title}", file=sys.stderr)
|
||||
img_url = search_and_scrape(title)
|
||||
time.sleep(0.3)
|
||||
|
||||
if img_url:
|
||||
print(f" Found: {img_url[:80]}", file=sys.stderr)
|
||||
if download_image(img_url, dest):
|
||||
print(f" Downloaded ({dest.stat().st_size} bytes)", file=sys.stderr)
|
||||
else:
|
||||
print(f" Download failed, will still add frontmatter", file=sys.stderr)
|
||||
else:
|
||||
print(f" No image found, will still add frontmatter", file=sys.stderr)
|
||||
|
||||
# Add frontmatter (with or without image)
|
||||
add_frontmatter(fp, slug, title)
|
||||
print(f" Done", file=sys.stderr)
|
||||
|
||||
print("\n=== Task 3 complete ===", file=sys.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
while IFS='|' read -r file slug; do
|
||||
new_url="https://roux.roo.lol/recipe-images/$slug.webp"
|
||||
sed -i "s|^image:.*|image: $new_url|" "$file"
|
||||
echo "[$slug] Updated image field"
|
||||
done
|
||||
@@ -0,0 +1,45 @@
|
||||
---
|
||||
title: Blackened Sheet Pan Salmon
|
||||
subtitle: with spring onions, potatoes, carrots & buttermilk-herb drizzle
|
||||
author: Chef Torie Cox
|
||||
servings: 2
|
||||
prep time: 20 minutes
|
||||
cook time: 35 minutes
|
||||
time: 35 minutes
|
||||
allergens: [fish, dairy, egg]
|
||||
source: /recipe-images/Scan_20260527_173752.jpg
|
||||
image: /recipe-images/blackened-sheet-pan-salmon.jpg
|
||||
|
||||
------
|
||||
|
||||
== Cook Veggies ==
|
||||
|
||||
Preheat oven to 425°F.
|
||||
|
||||
Dice @potatoes{2%cups} into 1/2" pieces.
|
||||
|
||||
Trim root and slice bulb of @spring onion{1}, then chop green tops into 1" pieces.
|
||||
|
||||
Zest HALF of @lemon{1} [- whole lemon for meal kits serving 4 -], cut remainder into wedges for garnish.
|
||||
|
||||
On a #sheet pan{}, toss @potatoes{}, @carrot coins{6%oz}, @spring onion{} bulb & tops with HALF of @thyme sprigs{1%tsp}, @oil{2%tbsp} [- 3 tbsp -], @salt{} & @pepper{}, then spread veggies out in an even layer [- use a second sheet pan if necessary -] and roast for ~{15%minutes}.
|
||||
|
||||
== Make Sauce ==
|
||||
|
||||
Pick leaves from remaining @thyme{} and chop.
|
||||
|
||||
Chop @chives{} & @dill{}.
|
||||
|
||||
In a #bowl{}, whisk together @Buttermilk Sauce Starter{2%tbsp}(buttermilk), @mayonnaise{2%tbsp}, @grainy dijon mustard{1/4%tsp}, @Creole seasoning{1/2%tsp}, chopped herbs and lemon zest.
|
||||
|
||||
== Cook Salmon ==
|
||||
|
||||
Pat @salmon fillets{2} dry, then season with @Creole seasoning{1%tsp}.
|
||||
|
||||
After veggies have been roasting for ~{15%minutes}, toss, then place @salmon{} skin-side down next to the veggies.
|
||||
|
||||
Cut @butter{1%tbsp} [- 2 tbsp -] into small cubes, dot salmon with butter, then bake ~{8-12%minutes}, until salmon is done and veggies are tender.
|
||||
|
||||
== Garnish & Gather ==
|
||||
|
||||
Divide veggies among plates and top with salmon. Serve with buttermilk sauce for drizzling and garnish with lemon wedges.
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
title: Bridget Jones's Pan-Fried Salmon with Pine Nut Salsa
|
||||
source: Simple - Ottolenghi
|
||||
description: A vibrant pan-fried salmon with a sweet and savoury pine nut salsa featuring currants, capers, olives, and saffron.
|
||||
servings: 4 servings
|
||||
tags: Pan-Fried, Seafood, Salmon, Dinner, Fish
|
||||
image: /recipe-images/bridget-jones-s-pan-fried-salmon-with-pine-nut-salsa.jpg
|
||||
---
|
||||
|
||||
Cover @currants{3/4%cup} with boiling water and set aside to soak for ~{20%minutes} while you prep the salmon and make the salsa.
|
||||
|
||||
Mix @salmon fillets{4}(skin on and pinbones removed) with 1 tbsp of the @olive oil{7%tbsp}{1%tbsp}, a rounded ¼ tsp salt, and a good grind of @black pepper{}. Set aside while you make the salsa.
|
||||
|
||||
Put 5 tbsp of the @olive oil{7%tbsp}{5%tbsp} into a #large sauté pan{} and place on a high heat. Add @celery stalks{4}(medium, cut into 1/2-inch dice, leaves kept for garnish) and @pine nuts{1/4%cup}(roughly chopped) and fry for ~{4-5%minutes}, stirring frequently, until the nuts begin to brown (don't take your eyes off them, as they can easily burn). Remove the pan from the heat and stir in @capers{1/4%cup}(plus 2 tbsp of their brine), @green olives{1/3%cup}(large, pitted and cut into 1/2-inch dice), @saffron threads{1%good pinch}(mixed with 1 tbsp hot water) and its water, and a pinch of @salt{}. Drain the currants and add these, along with @parsley{1%cup}(roughly chopped), lemon zest from @lemon{1}, and lemon juice from @lemon{}. Set aside.
|
||||
|
||||
Put the remaining 1 tbsp of @olive oil{7%tbsp}{1%tbsp} into a #large frying pan{} and place over medium-high heat. Once hot, add the @salmon fillets{}, skin side down, and fry for ~{3%minutes}, until the skin is crisp. Decrease the heat to medium, then flip the fillets over and continue to fry for ~{2-4%minutes} (depending on how much you like the salmon cooked). Remove from the pan and set aside.
|
||||
|
||||
Arrange the salmon on four plates and spoon on the salsa. Scatter the reserved celery leaves on top.
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
title: Butternut & Turkey Chili
|
||||
subtitle: with white beans, spiced pepitas & queso fresco
|
||||
author: Chef Torie Cox
|
||||
servings: 2
|
||||
time: 40 minutes
|
||||
prep time: 20 minutes
|
||||
tags:
|
||||
- meat
|
||||
- turkey
|
||||
- chili
|
||||
- soup
|
||||
- fall
|
||||
diet:
|
||||
- gluten-free
|
||||
description: A hearty chili with butternut squash, ground turkey, white beans, and spiced pepitas topped with queso fresco.
|
||||
category: dinner
|
||||
image: https://roux.roo.lol/recipe-images/butternut--turkey-chili.webp
|
||||
---
|
||||
|
||||
== Toast Pepitas & Prep Veggies ==
|
||||
|
||||
In a #pot large enough to cook the chili in later{}, heat @olive oil{1%tbsp} over med heat.
|
||||
|
||||
Add @pepitas{1/4%cup}, @smoked paprika{1/4%tsp}, @cayenne{1/8%tsp}, @cinnamon{1/8%tsp} and a pinch of @salt{}.
|
||||
|
||||
Toast, stirring frequently, until @pepitas{} smell fragrant and are lightly browned, ~{3-5%minutes}, then remove from #pot and set aside.
|
||||
|
||||
Remove seeds and chop @poblano pepper{3/4%cup}.
|
||||
|
||||
Chop @cilantro{2%tbsp}.
|
||||
|
||||
Drain and rinse @cannellini beans{1,1/2%cups}.
|
||||
|
||||
Cut @lime{1} into wedges.
|
||||
|
||||
== Prep & Cook Chili ==
|
||||
|
||||
In the same #pot used for pepitas{}, heat @olive oil{2%tbsp} over med-high heat.
|
||||
|
||||
Add @ground turkey{8%oz} and @salt{1/4%tsp} and @pepper{1/4%tsp} and cook ~{5-8%minutes}, until browned, breaking up with a #wooden spoon, then remove.
|
||||
|
||||
To the same #pot, add @butternut squash{1,1/2%cups}(diced), @onion{2%tbsp}, @garlic{1%tbsp} and @poblano pepper{} and cook until beginning to soften, ~{5-7%minutes}.
|
||||
|
||||
Add @chili powder{1%tbsp}, @cumin{2%tsp}, @smoked paprika{1%tsp}, @dried oregano{1/2%tsp}, and @cayenne{1/2%tsp} and cook ~{1%minute} or until fragrant. > HOT! add cayenne to taste.
|
||||
|
||||
Stir in @chicken broth{1/2%cup}, @crushed tomatoes{1%cup}, @water{1/2%cup}, cooked @turkey{}, @beans{} and @salt{1/2%tsp}, bring to a boil, then reduce heat to low and cook until @butternut squash{} is tender, ~{20%minutes}.
|
||||
|
||||
Crumble @queso fresco{2%tbsp}.
|
||||
|
||||
Adjust chili seasoning to taste.
|
||||
|
||||
== Garnish & Gather ==
|
||||
|
||||
Serve chili in #bowls topped with spiced @pepitas{}, @sour cream{3%tbsp}, @queso fresco{}, @cilantro{} and @lime wedges{}.
|
||||
@@ -1,47 +0,0 @@
|
||||
>> title: Butternut & Turkey Chili
|
||||
>> author: Chef Torie Cox
|
||||
>> servings: 2
|
||||
>> totalTime: 40 minutes
|
||||
>> tags: chili, turkey, butternut squash
|
||||
|
||||
# Toast pepitas & prep veggies
|
||||
|
||||
In a large #pot heat 1 tbsp @olive oil over medium heat.
|
||||
Add 1/4 cup @pepitas, 1/4 tsp @smoked paprika, 1/8 tsp @cayenne, 1/8 tsp @cinnamon, and a pinch of @salt.
|
||||
Toast, stirring frequently, until fragrant and lightly browned, about ~3-5 minutes.
|
||||
Transfer pepitas to a #bowl and set aside.
|
||||
|
||||
Remove seeds from 3/4 cup @sweet pepper and chop.
|
||||
Finely chop 2 tbsp @onion and 1 tbsp @garlic if not prepped.
|
||||
Chop 2 tbsp @cilantro.
|
||||
Drain and rinse 1 cup @cannellini beans.
|
||||
Cut 1 @lime into wedges.
|
||||
|
||||
---
|
||||
|
||||
# Prep & cook chili
|
||||
|
||||
In the same #pot heat 2 tbsp @olive oil over medium-high heat.
|
||||
Add 8 oz @ground turkey and season with 1/4 tsp @salt and 1/4 tsp @black pepper.
|
||||
Cook, breaking up with a #wooden spoon, until browned, about ~5-8 minutes.
|
||||
Remove turkey from pot and set aside.
|
||||
|
||||
To the #pot add 1 1/2 cups @butternut squash (diced), 2 tbsp @onion (if not already in), 1 tbsp @garlic (if not already in), and the chopped @sweet pepper.
|
||||
Cook, stirring occasionally, until beginning to soften, about ~5-7 minutes.
|
||||
|
||||
Add 1 tsp @chili powder, 2 tsp @cumin, 1 tsp @smoked paprika, and 1/4 tsp @dried oregano.
|
||||
Cook until fragrant, about ~1 minute.
|
||||
|
||||
Stir in 1 container @crushed tomatoes, 1/2 cup @chicken broth, 1/2 cup @water, the cooked @ground turkey, the drained @cannellini beans, and 1/2 tsp @salt.
|
||||
Bring to a boil, then reduce heat to low and simmer until butternut is tender, about ~20 minutes.
|
||||
Taste and adjust seasoning with additional @salt and @black pepper.
|
||||
|
||||
Crumble 2 tbsp @queso fresco in a small #bowl.
|
||||
|
||||
---
|
||||
|
||||
# Garnish & serve
|
||||
|
||||
Ladle chili into #bowls.
|
||||
Top each serving with spiced @pepitas, 3 tbsp @sour cream, crumbled @queso fresco, chopped @cilantro, and @lime wedges.
|
||||
Serve hot.
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
title: Cajun Butter Mahi with Corn & Okra Maque Choux over Carolina Gold Rice
|
||||
servings: 2
|
||||
prep time: 30 minutes
|
||||
time: 45 minutes
|
||||
author: Chef Torie Cox
|
||||
cuisine: Cajun
|
||||
tags:
|
||||
- fish
|
||||
- seafood
|
||||
- rice
|
||||
description: Cajun-seasoned mahi mahi served with a corn and okra maque choux over Carolina Gold rice, drizzled with a Cajun butter sauce.
|
||||
source: /recipe-images/document_1_.pdf
|
||||
------
|
||||
|
||||
== Cook Rice ==
|
||||
|
||||
In a #pot, heat @oil{1%tbsp} over medium heat. Add @Carolina Gold rice{3/4%cup} and stir to toast for ~{2%minutes}.
|
||||
|
||||
Add @water{1,1/4%cups} and @salt{} and bring to a boil, then reduce heat to low, cover and cook for ~{15%minutes}. Remove from heat and let stand for ~{10%minutes}.
|
||||
|
||||
== Cook Maque Choux & Make Cajun Butter ==
|
||||
|
||||
Slice @okra{1%cup} into 1/4" thick rounds. Remove core and cut @sweet pepper{1%cup} into 1/2" dice. Cut corn kernels from the cobs of @corn{2%ears}. Slice @scallion{1}.
|
||||
|
||||
In a #large sauté pan{} over medium heat, add @oil{1%tbsp}, the diced pepper and @onion{1/2%cup} and cook for ~{5%minutes} or until beginning to soften.
|
||||
|
||||
Add the corn, okra and half of the @garlic{1,1/2%tsp}, cook for ~{5-7%minutes} until tender, then season with @salt{} and @pepper{}. Remove to a #bowl and wipe out the pan.
|
||||
|
||||
Melt @butter{1%oz} in a #small bowl{} in the microwave, then stir in half of the @Cajun seasoning{1%tbsp}, the remaining garlic, and salt. -- this is the Cajun butter sauce
|
||||
|
||||
== Cook Fish ==
|
||||
|
||||
In the same #sauté pan{}, heat @oil{1%tbsp} over medium-high heat. Pat the @mahi mahi fillets{2} dry and season with the remaining Cajun seasoning, @salt{}, and @pepper{}.
|
||||
|
||||
Add the fish to the hot pan and cook ~{2-3%minutes} per side, or until desired doneness.
|
||||
|
||||
Add the sliced scallion to the rice and fluff with a fork.
|
||||
|
||||
== Garnish & Serve ==
|
||||
|
||||
Divide the fish, rice, and maque choux among plates, then drizzle with the Cajun butter sauce.
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
title: Carbonara
|
||||
source: //cooking.nytimes.com/recipes/12965-spaghetti-carbonara
|
||||
description: This dish is a deli bacon-egg-and-cheese-on-a-roll that has been pasta-fied, fancified, fetishized and turned into an Italian tradition that, like many inviolate Italian traditions, is actually far less old than the Mayflower. Because America may have contributed to its creation, carbonara is Exhibit A in the back-and-forth between Italy and the United States when it comes to food. Remember: the main goal is creaminess.
|
||||
author: Ian Fisher
|
||||
course: Dinner, Main Course, Pastas
|
||||
cuisine: Italian
|
||||
servings: 4 servings
|
||||
totalTime: 30 minutes
|
||||
prepTime: 5 minutes
|
||||
cookTime: 25 minutes
|
||||
tags: Bacon, Cheese, Easy, Egg, Five Ingredient, Pancetta, Quick, Spaghetti
|
||||
---
|
||||
|
||||
== Ingredients ==
|
||||
, @large eggs and 2 large yolks{2}, @(about ⅓ packed cup) grated pecorino Romano{1%ounce}, @(about ⅓ packed cup) grated Parmesan{1%ounce}, @Coarsely ground black pepper, @olive oil{1%tablespoon}, @½ ounces of slab guanciale (see recipe){3}, @spaghetti (about ¾ box){12%ounces}
|
||||
== Method ==
|
||||
Place a large pot of lightly salted water (no more than 1 tablespoon salt) over high heat, and bring to a boil. Fill a large bowl with hot water (to warm the bowl for serving), and set aside.
|
||||
|
||||
In a mixing bowl, whisk together the eggs, yolks and pecorino and Parmesan. Season with a pinch of salt and generous black pepper.
|
||||
|
||||
Set the water to boil. Meanwhile, heat oil in a large skillet over medium heat, add the pork, and sauté until the fat just renders, on the edge of crispness but not hard. Remove from heat and set aside.
|
||||
|
||||
Add pasta to the water and boil until a bit firmer than al dente. Just before pasta is ready, reheat guanciale in skillet, if needed. Reserve 1 cup of pasta water, then drain pasta and add to the skillet over low heat. Stir for a minute or so.
|
||||
|
||||
Empty serving bowl of hot water. Dry it and add hot pasta mixture. Stir in cheese mixture, adding some reserved pasta water if needed for creaminess. Serve immediately, dressing it with a bit of additional grated pecorino and pepper.
|
||||
+554
@@ -0,0 +1,554 @@
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@id": "urn:nyt:ScoopRecipe:c32379cbd70d591f9932fec6ad7497ce",
|
||||
"@type": "Recipe",
|
||||
"aggregateRating": {
|
||||
"@type": "AggregateRating",
|
||||
"ratingCount": 17150,
|
||||
"ratingValue": 5
|
||||
},
|
||||
"alternateName": "Spaghetti Carbonara",
|
||||
"author": {
|
||||
"@id": "nyt://person/4c04b52b-86cf-5da4-8a5a-2142526e3e16",
|
||||
"@type": "Person",
|
||||
"description": "Ian Fisher worked for The New York Times from 1990 to 2017, reporting from nearly 60 countries and serving as bureau chief in East Africa, Eastern Europe, Rome and Jerusalem. He later returned to New York, where he served as deputy foreign editor, as a masthead editor in charge of The Times’s digital transition, as head of investigations, and as weekend editor. His final role at The Times was Jerusalem bureau chief.",
|
||||
"image": {
|
||||
"@id": "nyt://image/f8d237e1-0aab-5e9a-8de4-db3fd3b20b91#mediumSquareAt3X",
|
||||
"@type": "ImageObject",
|
||||
"contentUrl": "https://static01.nyt.com/images/2026/03/24/reader-center/author-ian-fisher/author-ian-fisher-mediumSquareAt3X.png",
|
||||
"dateModified": "2026-03-24T14:49:22.379Z",
|
||||
"datePublished": "2026-03-24T14:49:22.379Z",
|
||||
"height": "1000",
|
||||
"uploadDate": "2026-03-24T14:49:22.379Z",
|
||||
"url": "https://static01.nyt.com/images/2026/03/24/reader-center/author-ian-fisher/author-ian-fisher-mediumSquareAt3X.png",
|
||||
"width": "1000"
|
||||
},
|
||||
"name": "Ian Fisher",
|
||||
"sameAs": [
|
||||
"https://x.com/fisheri",
|
||||
"https://www.facebook.com/ian.fisher.5661"
|
||||
],
|
||||
"url": "https://www.nytimes.com/by/ian-fisher"
|
||||
},
|
||||
"cookTime": "PT0H25M",
|
||||
"cookingMethod": "Boil, Saute",
|
||||
"copyrightHolder": {
|
||||
"@id": "https://cooking.nytimes.com/#publisher",
|
||||
"@type": "Organization",
|
||||
"name": "NYT Cooking"
|
||||
},
|
||||
"copyrightYear": 2012,
|
||||
"dateModified": "2026-04-06T17:54:34.901Z",
|
||||
"datePublished": "2012-11-21T00:00:00Z",
|
||||
"description": "This dish is a deli bacon-egg-and-cheese-on-a-roll that has been pasta-fied, fancified, fetishized and turned into an Italian tradition that, like many inviolate Italian traditions, is actually far less old than the Mayflower. Because America may have contributed to its creation, carbonara is Exhibit A in the back-and-forth between Italy and the United States when it comes to food. Remember: the main goal is creaminess.",
|
||||
"image": [
|
||||
{
|
||||
"@id": "nyt://image/9458a0d9-8671-5969-9f6b-e4ee4d51b701#videoSixteenByNineJumbo1600",
|
||||
"@type": "ImageObject",
|
||||
"caption": "Ian Fisher's spaghetti carbonara.",
|
||||
"contentUrl": "https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-videoSixteenByNineJumbo1600-v2.jpg",
|
||||
"creditText": "Linda Xiao for The New York Times. Food Stylist: Sarah Jampel.",
|
||||
"dateModified": "2025-01-21T21:27:32.236Z",
|
||||
"datePublished": "2018-08-10T20:21:25.000Z",
|
||||
"height": "899",
|
||||
"uploadDate": "2018-08-10T20:21:25.000Z",
|
||||
"url": "https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-videoSixteenByNineJumbo1600-v2.jpg",
|
||||
"width": "1600"
|
||||
},
|
||||
{
|
||||
"@id": "nyt://image/9458a0d9-8671-5969-9f6b-e4ee4d51b701#googleFourByThree",
|
||||
"@type": "ImageObject",
|
||||
"caption": "Ian Fisher's spaghetti carbonara.",
|
||||
"contentUrl": "https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-googleFourByThree-v2.jpg",
|
||||
"creditText": "Linda Xiao for The New York Times. Food Stylist: Sarah Jampel.",
|
||||
"dateModified": "2025-01-21T21:27:32.236Z",
|
||||
"datePublished": "2018-08-10T20:21:25.000Z",
|
||||
"height": "600",
|
||||
"uploadDate": "2018-08-10T20:21:25.000Z",
|
||||
"url": "https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-googleFourByThree-v2.jpg",
|
||||
"width": "800"
|
||||
},
|
||||
{
|
||||
"@id": "nyt://image/9458a0d9-8671-5969-9f6b-e4ee4d51b701#mediumSquareAt3X",
|
||||
"@type": "ImageObject",
|
||||
"caption": "Ian Fisher's spaghetti carbonara.",
|
||||
"contentUrl": "https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-mediumSquareAt3X-v2.jpg",
|
||||
"creditText": "Linda Xiao for The New York Times. Food Stylist: Sarah Jampel.",
|
||||
"dateModified": "2025-01-21T21:27:32.236Z",
|
||||
"datePublished": "2018-08-10T20:21:25.000Z",
|
||||
"height": "1417",
|
||||
"uploadDate": "2018-08-10T20:21:25.000Z",
|
||||
"url": "https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-mediumSquareAt3X-v2.jpg",
|
||||
"width": "1419"
|
||||
},
|
||||
{
|
||||
"@id": "nyt://image/9458a0d9-8671-5969-9f6b-e4ee4d51b701#facebookJumbo",
|
||||
"@type": "ImageObject",
|
||||
"caption": "Ian Fisher's spaghetti carbonara.",
|
||||
"contentUrl": "https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-facebookJumbo-v2.jpg",
|
||||
"creditText": "Linda Xiao for The New York Times. Food Stylist: Sarah Jampel.",
|
||||
"dateModified": "2025-01-21T21:27:32.236Z",
|
||||
"datePublished": "2018-08-10T20:21:25.000Z",
|
||||
"height": "549",
|
||||
"uploadDate": "2018-08-10T20:21:25.000Z",
|
||||
"url": "https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-facebookJumbo-v2.jpg",
|
||||
"width": "1050"
|
||||
}
|
||||
],
|
||||
"isAccessibleForFree": true,
|
||||
"keywords": "Bacon, Cheese, Easy, Egg, Five Ingredient, Pancetta, Quick, Spaghetti",
|
||||
"mainEntityOfPage": {
|
||||
"@id": "//cooking.nytimes.com/recipes/12965-spaghetti-carbonara",
|
||||
"@type": "WebPage",
|
||||
"name": "Carbonara"
|
||||
},
|
||||
"name": "Carbonara",
|
||||
"nutrition": {
|
||||
"@type": "NutritionInformation",
|
||||
"calories": "534.5 kcals",
|
||||
"carbohydrateContent": "64.4 grams",
|
||||
"cholesterolContent": "121.6 milligrams",
|
||||
"fatContent": "19.9 grams",
|
||||
"fiberContent": "2.8 grams",
|
||||
"proteinContent": "22.4 grams",
|
||||
"saturatedFatContent": "6.9 grams",
|
||||
"sodiumContent": "412 milligrams",
|
||||
"sugarContent": "2.5 grams",
|
||||
"unsaturatedFatContent": "2.8 grams"
|
||||
},
|
||||
"prepTime": "PT0H5M",
|
||||
"publisher": {
|
||||
"@id": "https://cooking.nytimes.com/#publisher",
|
||||
"@type": "Organization",
|
||||
"name": "NYT Cooking"
|
||||
},
|
||||
"recipeCategory": "Dinner, Main Course, Pastas",
|
||||
"recipeCuisine": "Italian",
|
||||
"recipeIngredient": [
|
||||
"Salt ",
|
||||
"2 large eggs and 2 large yolks, room temperature ",
|
||||
"1 ounce (about ⅓ packed cup) grated pecorino Romano, plus additional for serving ",
|
||||
"1 ounce (about ⅓ packed cup) grated Parmesan ",
|
||||
"Coarsely ground black pepper ",
|
||||
"1 tablespoon olive oil ",
|
||||
"3 ½ ounces of slab guanciale (see recipe), pancetta or bacon, sliced into pieces about ¼ inch thick by 1⅓ inch square ",
|
||||
"12 ounces spaghetti (about ¾ box) "
|
||||
],
|
||||
"recipeInstructions": [
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
"text": "Place a large pot of lightly salted water (no more than 1 tablespoon salt) over high heat, and bring to a boil. Fill a large bowl with hot water (to warm the bowl for serving), and set aside.",
|
||||
"url": "https://cooking.nytimes.com/recipes/12965-spaghetti-carbonara#recipe-step-1"
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
"text": "In a mixing bowl, whisk together the eggs, yolks and pecorino and Parmesan. Season with a pinch of salt and generous black pepper.",
|
||||
"url": "https://cooking.nytimes.com/recipes/12965-spaghetti-carbonara#recipe-step-2"
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
"text": "Set the water to boil. Meanwhile, heat oil in a large skillet over medium heat, add the pork, and sauté until the fat just renders, on the edge of crispness but not hard. Remove from heat and set aside.",
|
||||
"url": "https://cooking.nytimes.com/recipes/12965-spaghetti-carbonara#recipe-step-3"
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
"text": "Add pasta to the water and boil until a bit firmer than al dente. Just before pasta is ready, reheat guanciale in skillet, if needed. Reserve 1 cup of pasta water, then drain pasta and add to the skillet over low heat. Stir for a minute or so.",
|
||||
"url": "https://cooking.nytimes.com/recipes/12965-spaghetti-carbonara#recipe-step-4"
|
||||
},
|
||||
{
|
||||
"@type": "HowToStep",
|
||||
"text": "Empty serving bowl of hot water. Dry it and add hot pasta mixture. Stir in cheese mixture, adding some reserved pasta water if needed for creaminess. Serve immediately, dressing it with a bit of additional grated pecorino and pepper.",
|
||||
"url": "https://cooking.nytimes.com/recipes/12965-spaghetti-carbonara#recipe-step-5"
|
||||
}
|
||||
],
|
||||
"recipeYield": "4 servings",
|
||||
"review": [
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "ELBSeattle"
|
||||
},
|
||||
"datePublished": "2026-06-14T02:42:35.000Z",
|
||||
"reviewBody": "About the garlic debate: if you like garlic, add it. If you don’t, don’t. I think 100% authenticity would only matter if this dish were being made in a competition, for a grade in a culinary school or if one is a chef at an Italian restaurant. Otherwise, I think it is 100% fine to color outside if the lines when cooking fir one’s self, family and friends. (My version of gâteau basque has a very inauthentic layer of coconut cream, and no one has complained.)"
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Tea Totaler Adjacent"
|
||||
},
|
||||
"datePublished": "2026-06-13T17:48:33.000Z",
|
||||
"reviewBody": "I use Fontinella instead of Romano. Very hard to find though."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Richard X"
|
||||
},
|
||||
"datePublished": "2026-06-13T21:33:07.000Z",
|
||||
"reviewBody": "My family prefers a portion size of about 4 oz. We don't \"look at packages\" for dining tips."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Rick Davidson"
|
||||
},
|
||||
"datePublished": "2026-06-13T16:37:51.000Z",
|
||||
"reviewBody": "Look at the package. A single portion of pasta is 2-2.5 oz."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Pat"
|
||||
},
|
||||
"datePublished": "2026-06-13T05:34:10.000Z",
|
||||
"reviewBody": "you need to temper a bit of the egg and cheese mixture into the pasta rather than dumping it in all at once- otherwise you will have scrambled eggs. I have also started using Trader Joe’s pancetta for an easier to make dish, topped with parsley to finish"
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Barbara"
|
||||
},
|
||||
"datePublished": "2026-06-13T03:38:24.000Z",
|
||||
"reviewBody": "PLEASE! NO CREAM!"
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Richard X"
|
||||
},
|
||||
"datePublished": "2026-06-13T12:27:09.000Z",
|
||||
"reviewBody": "The water that was in the bowl has now cooled a bit, and we don't want that. A quick wipe of the bowl is not a burdensome step. Nit-picky indeed."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Equilibrist"
|
||||
},
|
||||
"datePublished": "2026-06-13T00:00:57.000Z",
|
||||
"reviewBody": "This is nit-picky, I know, but why would you dry the bowl that has had hot water in it before adding a pasta and pork mix that is just going to have more water added? I guess I just like it when recipes are streamlined, and this adds a completely unnecessary step."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Susan"
|
||||
},
|
||||
"datePublished": "2026-06-12T22:52:52.000Z",
|
||||
"reviewBody": "Good tips on how not to make \"scrambled egg\" pasta."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Showsomebackbone"
|
||||
},
|
||||
"datePublished": "2026-06-12T22:02:38.000Z",
|
||||
"reviewBody": "Huh! Guanciale looks a lot like salt pork lol. Goes to show similarities in foods across cultures/countries. \nHas anyone ever included sautéed (in the bacon fat), diced, sweet onion?"
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Bailey"
|
||||
},
|
||||
"datePublished": "2026-06-12T22:31:27.000Z",
|
||||
"reviewBody": "Considering what a disaster getting ingredients to make this ended up being (for context I made this at 6pm, my almost 3 year old happily playing, so I could not leave to fix the errors) when I asked someone to grab the few things I didn’t already have on hand (the cheese, and bacon) and ended up with pre cooked bacon (why 😭) and the powdered cheese you get in a green bottle\n(Double why), I still made this dish and it ended up being very good! Will definitely\nMake again with the proper ingredients, but even without, this one gets 5 stars!"
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Pete E. Pye"
|
||||
},
|
||||
"datePublished": "2026-06-12T20:11:55.000Z",
|
||||
"reviewBody": "This recipe is fine as is, and the tips below are excellent. I add a 1/2 cup of white wine at the step where I reheat the pancetta, to deglaze the pan and also cause ya know, wine."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Richard"
|
||||
},
|
||||
"datePublished": "2026-06-14T09:19:10.000Z",
|
||||
"reviewBody": "Every recipe should \"triple the garlic.\" Ask any NYT Cooking readers."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Lisa"
|
||||
},
|
||||
"datePublished": "2026-06-12T19:22:48.000Z",
|
||||
"reviewBody": "Spaghetti carbonara does not have garlic in it. Ask any Italian."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Rockothecat"
|
||||
},
|
||||
"datePublished": "2026-06-12T17:38:59.000Z",
|
||||
"reviewBody": "My father severed in the US Army during WWII from Napoli, Roma, Southern France and the Rhineland. He learned to speak Italian, while there and he talked about a dish the locals made for the US solders, pasta alla carbonara, with bacon, egg and cheese, to remind the soldiers of an American breakfast they would have had back home,\nAfter the war, my father went to college on the GI bill, got a Masters in Agricultural Economics and was posted to the US Embassy in Rome in 1958 where I was born."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "rockothecatcat"
|
||||
},
|
||||
"datePublished": "2026-06-12T17:52:33.000Z",
|
||||
"reviewBody": "While in Roma,1958, my mother learned a local recipe for pasta alla carbonara: slab cured pork, egg, diced onion, parsley, Parmesan cheese, pepper, red wine. I use thick cut bacon. How to: bacon and onion saute till light brown, pour off excess fat, set aside, boil spaghetti al dente, in a bowl, beat one egg, add copped parsley, fresh ground pepper, set aside. Heat up pan again, use red wine to deglaze. Place hot drained spaghetti in bowl, mix, add deglaze bacon/onion, sprinkle cheese, serve."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Kevin"
|
||||
},
|
||||
"datePublished": "2026-06-12T17:30:31.000Z",
|
||||
"reviewBody": "Toss the hot pasta in the bowl with the egg and cheese mixture. Then stir in the cooked guanciale or pancetta."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "ken"
|
||||
},
|
||||
"datePublished": "2026-06-12T17:35:59.000Z",
|
||||
"reviewBody": "I prefer to use a large plastic mixing bowl or salad bowl to beat the eggs and cheese. If you mix the pasta into that the pasta will stay warmer than if you use a metal or ceramic bowl (even if you warm the bowl). Heat the individual pasta bowls. Cold carbonara is an abomination, or at least a different dish."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Julian Fernandez"
|
||||
},
|
||||
"datePublished": "2017-10-30T15:18:00.000Z",
|
||||
"reviewBody": "Never combine the ingredients in the pan you have had on the burner. The eggs will cook and harden. Place a stainless steel mixing bowl under your colander and let the pasta water heat the bowl. Discard the water and use the heated bowl to combine all ingredients. Toss for at least two minutes."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Julian Fernandez"
|
||||
},
|
||||
"datePublished": "2017-10-30T15:20:22.000Z",
|
||||
"reviewBody": "At Step 2, a clove or two of minced or pressed garlic should be added to the egg and cheese mixture. I've never seen a carbonara recipe that did not include garlic.<br/><br/>At Step 3, you can replace the olive oil with 1/4 C. water. Sounds crazy, but the bacon will remain tender and flavorful. After it has cooled slightly, whisk one or two tablespoons of rendered bacon fat, in a slow stream, into the egg/cheese/garlic mixture. Much creamier and flavorful."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Max H."
|
||||
},
|
||||
"datePublished": "2017-10-24T15:12:50.000Z",
|
||||
"reviewBody": "This recipe is spot on. My wife thinks I'm a pro when I cook it :) I do have 1 tip though. Don't be afraid to add the starchy water at the end. Doing so makes it more smoothy and creamy. It might seem a little too wet at first but it will cool down and be just right. The first 2 times I made this dish I only added a 1/4 cup of water because it looked wet enough. However once the dish cooled it got a bit sticky and dry. I regretted not adding more water and learned my lesson."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Alexa H"
|
||||
},
|
||||
"datePublished": "2017-10-30T15:51:39.000Z",
|
||||
"reviewBody": "Just of interest, where is the garlic that is mentioned so frequently in the reviews? In eating Roman spaghetti carbonara there was absolutely no garlic. Much food in Italy has very spare amounts of or even no garlic. It is in America that we add so much garlic to Italian food."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Mark"
|
||||
},
|
||||
"datePublished": "2017-10-24T17:24:51.000Z",
|
||||
"reviewBody": "Good version of carbonara. I generally buy pasteurized eggs when I make this. I prefer all yolks rather than whole eggs. I don't generally buy pecorino, so I use all parmesan. If you use bacon, don't get anything too heavily smoked. I generally use bacon because pancetta is triple the cost and guanciale is even more costly. And for goodness sake, why on earth would anybody create a recipe for 3/4 lb pasta. Toss in another egg and a few additional slices of bacon."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Steve Muni"
|
||||
},
|
||||
"datePublished": "2017-10-24T19:47:24.000Z",
|
||||
"reviewBody": "I also saute half a chopped onion with the pancetta, and add half a cup of chopped parsley at the very end. That's the way I was taught by my mother, who learned it from the cook at a little Rome trattoria in 1951."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Cheryl"
|
||||
},
|
||||
"datePublished": "2017-10-30T15:55:20.000Z",
|
||||
"reviewBody": "Regardless of whether it should have garlic or shouldn't, whether cream negates it as carbonara or not, whether eggs are necessary (um, yes), this is delicious. I went on a carbonara kick a few weeks ago, and I've tried out eleven recipes so far, and this is my favorite by far for its ease and quickness."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "kate"
|
||||
},
|
||||
"datePublished": "2017-10-01T19:30:36.000Z",
|
||||
"reviewBody": "You need to add the HOT pasta to the egg/cheese mixture to slightly/lightly cook it, and to coat the pasta with the \"creamy\" mixture. If you had pasta to bacon grease FIRST, the egg/cheese mixture will not adhere to the pasta, and you will end up with a lumpy mess in the bottom of the bowl-- it will slide off the pasta.<br/>Speaking as someone who grew up in Rome, (a) capitol of Carbonara."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Paolo"
|
||||
},
|
||||
"datePublished": "2017-10-01T19:29:04.000Z",
|
||||
"reviewBody": "Recipes are guidelines people! If the end result is good, who cares if it doesn't conform exactly to what your grandmother did. More than likely her version varied depending on what was around. I can't believe people become angry over varieties of grated cheese."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Publius"
|
||||
},
|
||||
"datePublished": "2017-10-24T18:43:15.000Z",
|
||||
"reviewBody": "NO!!! No garlic!!!!"
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Elisabeth P"
|
||||
},
|
||||
"datePublished": "2017-10-30T15:51:07.000Z",
|
||||
"reviewBody": "Beat whole eggs (2 @ person?) in pasta bowl, add some olive oil or butter. Cook bacon pieces/pancetta, drain on paper towels; cook sliced onion & garlic, add bacon back in, add white wine or dry vermouth. Cook spaghetti, then spoon some hot pasta water into eggs, stirring, then using tongs put all hot spaghetti into eggs quickly, let sit two seconds, toss. Toss in bacon mixture, seasonings (parsley, oregano, hot pepper flakes?), more pasta water if dry, serve with grated pecorino."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "PTFogarty"
|
||||
},
|
||||
"datePublished": "2017-10-30T15:49:41.000Z",
|
||||
"reviewBody": "Thank you for not including cream or milk, as many \"so called\" carbonara recipes do. Cream makes it Alfredo. I fell in love with carbonara when I was stationed in Naples, Italy, with the US Navy. In my version, I only use Parmesan, not Romano, but often add a bit of garlic to the frying bacon.<br/><br/>And yes, eggs are necessary. The dish was invented at the end of WWII when Napolitans discovered the GI's powered eggs."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Patrizia"
|
||||
},
|
||||
"datePublished": "2017-10-01T15:28:38.000Z",
|
||||
"reviewBody": "I've lived in Rome. Never had a Carbonara made with garlic. I do like the version with onions,though. Authentic Roman Carbonara is always made with guanciale. We definitely dribbled the guanicale fat in with the egg mixture. Produces that gorgeously sensual lip slicking as you fork the twirled pasta into your mouth. I especially love carbonara with lots of coarse ground pepper--the 'carbon' of the recipe."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "Joe B."
|
||||
},
|
||||
"datePublished": "2017-10-30T14:48:37.000Z",
|
||||
"reviewBody": "Well, I suppose you could use eggbeaters. But then again, you could substitute smoked ham, Velveeta and noodles for the other three ingredients and get an acceptable dinner. It just wouldn't be Spaghetti Carbonara."
|
||||
},
|
||||
{
|
||||
"@type": "Review",
|
||||
"author": {
|
||||
"@type": "Person",
|
||||
"name": "greg b"
|
||||
},
|
||||
"datePublished": "2017-10-24T18:52:51.000Z",
|
||||
"reviewBody": "I made pork belly and had some leftover. I thought, \"why not make carbonara?\".<br/><br/>First, I used a wide noodle, parpardelle, and all of cheese was parmesan, but otherwise I followed this recipe to a tee as I've always made it that way.<br/><br/>The pork belly, sliced into lardons about 3 cm x 1 cm, were substituted for the bacon.<br/><br/>Mind blown...oh my goodness, this was the BEST version of this dish I have ever made or tasted."
|
||||
}
|
||||
],
|
||||
"totalTime": "PT0H30M",
|
||||
"url": "//cooking.nytimes.com/recipes/12965-spaghetti-carbonara",
|
||||
"video": {
|
||||
"@id": "nyt://video/6204b9e9-efa5-5ef5-94a5-28ccede9a986",
|
||||
"@type": "VideoObject",
|
||||
"author": {
|
||||
"@id": "nyt://person/86245e1a-0c21-50e7-943b-64a772deff66",
|
||||
"@type": "Person",
|
||||
"name": "Anne Alexander",
|
||||
"url": "https://www.nytimes.com/by/anne-alexander"
|
||||
},
|
||||
"bitrate": "0",
|
||||
"contentSize": "18773866",
|
||||
"contentUrl": "https://vp.nyt.com/video/2017/06/23/73101_1_recipe-carbonara_wg_480p.mp4",
|
||||
"dateModified": "2024-10-15T20:14:16.185Z",
|
||||
"datePublished": "2017-06-29T15:59:30.000Z",
|
||||
"description": "How to make a perfect version of this classic pasta.",
|
||||
"duration": "PT1M35S",
|
||||
"height": "480",
|
||||
"name": "Spaghetti Carbonara",
|
||||
"publisher": {
|
||||
"@id": "https://www.nytimes.com/#publisher",
|
||||
"@type": "NewsMediaOrganization",
|
||||
"name": "The New York Times"
|
||||
},
|
||||
"thumbnail": [
|
||||
{
|
||||
"@id": "nyt://image/9458a0d9-8671-5969-9f6b-e4ee4d51b701#videoSixteenByNineJumbo1600",
|
||||
"@type": "ImageObject",
|
||||
"caption": "Ian Fisher's spaghetti carbonara.",
|
||||
"contentUrl": "https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-videoSixteenByNineJumbo1600-v2.jpg",
|
||||
"creditText": "Linda Xiao for The New York Times. Food Stylist: Sarah Jampel.",
|
||||
"dateModified": "2025-01-21T21:27:32.236Z",
|
||||
"datePublished": "2018-08-10T20:21:25.000Z",
|
||||
"height": "899",
|
||||
"uploadDate": "2018-08-10T20:21:25.000Z",
|
||||
"url": "https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-videoSixteenByNineJumbo1600-v2.jpg",
|
||||
"width": "1600"
|
||||
},
|
||||
{
|
||||
"@id": "nyt://image/9458a0d9-8671-5969-9f6b-e4ee4d51b701#googleFourByThree",
|
||||
"@type": "ImageObject",
|
||||
"caption": "Ian Fisher's spaghetti carbonara.",
|
||||
"contentUrl": "https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-googleFourByThree-v2.jpg",
|
||||
"creditText": "Linda Xiao for The New York Times. Food Stylist: Sarah Jampel.",
|
||||
"dateModified": "2025-01-21T21:27:32.236Z",
|
||||
"datePublished": "2018-08-10T20:21:25.000Z",
|
||||
"height": "600",
|
||||
"uploadDate": "2018-08-10T20:21:25.000Z",
|
||||
"url": "https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-googleFourByThree-v2.jpg",
|
||||
"width": "800"
|
||||
},
|
||||
{
|
||||
"@id": "nyt://image/9458a0d9-8671-5969-9f6b-e4ee4d51b701#mediumSquareAt3X",
|
||||
"@type": "ImageObject",
|
||||
"caption": "Ian Fisher's spaghetti carbonara.",
|
||||
"contentUrl": "https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-mediumSquareAt3X-v2.jpg",
|
||||
"creditText": "Linda Xiao for The New York Times. Food Stylist: Sarah Jampel.",
|
||||
"dateModified": "2025-01-21T21:27:32.236Z",
|
||||
"datePublished": "2018-08-10T20:21:25.000Z",
|
||||
"height": "1417",
|
||||
"uploadDate": "2018-08-10T20:21:25.000Z",
|
||||
"url": "https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-mediumSquareAt3X-v2.jpg",
|
||||
"width": "1419"
|
||||
},
|
||||
{
|
||||
"@id": "nyt://image/9458a0d9-8671-5969-9f6b-e4ee4d51b701#facebookJumbo",
|
||||
"@type": "ImageObject",
|
||||
"caption": "Ian Fisher's spaghetti carbonara.",
|
||||
"contentUrl": "https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-facebookJumbo-v2.jpg",
|
||||
"creditText": "Linda Xiao for The New York Times. Food Stylist: Sarah Jampel.",
|
||||
"dateModified": "2025-01-21T21:27:32.236Z",
|
||||
"datePublished": "2018-08-10T20:21:25.000Z",
|
||||
"height": "549",
|
||||
"uploadDate": "2018-08-10T20:21:25.000Z",
|
||||
"url": "https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-facebookJumbo-v2.jpg",
|
||||
"width": "1050"
|
||||
}
|
||||
],
|
||||
"thumbnailUrl": [
|
||||
"https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-videoSixteenByNineJumbo1600-v2.jpg",
|
||||
"https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-googleFourByThree-v2.jpg",
|
||||
"https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-mediumSquareAt3X-v2.jpg",
|
||||
"https://static01.nyt.com/images/2021/02/14/dining/carbonara-horizontal/carbonara-horizontal-facebookJumbo-v2.jpg"
|
||||
],
|
||||
"transcript": "WG in master proj folder 73033",
|
||||
"uploadDate": "2017-06-29T15:59:30.000Z",
|
||||
"url": "https://www.nytimes.com/video/embedded/dining/100000005183614/spaghetti-carbonara.html",
|
||||
"width": "854"
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,11 @@ servings: 6 servings
|
||||
totalTime: 55 minutes
|
||||
prepTime: 15 minutes
|
||||
cookTime: 40 minutes
|
||||
tags: arugula, bread crumb, budget, cauliflower, parmesan, sheet-pan, fall, vegetarian
|
||||
tags:
|
||||
- vegetarian
|
||||
- italian
|
||||
- vegetable
|
||||
image: https://roux.roo.lol/recipe-images/cauliflower-milanese.webp
|
||||
---
|
||||
|
||||
== Ingredients ==
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
title: Chicken & Peach Chimichurri with Summer Squash Fritters
|
||||
servings: 2
|
||||
prep time: 35 minutes
|
||||
time: 50 minutes
|
||||
author: Chef Robin Pridgen
|
||||
description: Chicken breasts with a fresh peach chimichurri and homemade summer squash fritters.
|
||||
tags:
|
||||
- chicken
|
||||
- summer
|
||||
- fritters
|
||||
source: /recipe-images/document.pdf
|
||||
image: /recipe-images/chicken--peach-chimichurri-with-summer-squash-fritters.jpg
|
||||
------
|
||||
|
||||
== Prep Veggies & Cook Squash ==
|
||||
|
||||
Dice @summer squash{1,1/4%cup}(diced into 1/4" pieces) and thinly slice @green onion{1}.
|
||||
|
||||
In a #large nonstick sauté pan{}, heat @olive oil{1%tbsp} over med-high. Add the squash and @salt{} and sauté for ~{4-5%minutes} until softened, then remove, set aside to cool slightly, and wipe out the pan.
|
||||
|
||||
== Make Chimichurri ==
|
||||
|
||||
Remove the pit and dice @peach{1}(diced into 1/4" pieces). Remove seeds and mince half of @jalapeño{1/2}(minced) — save remainder for another use, or use all for more heat.
|
||||
|
||||
Chop @cilantro{2%tbsp} and @parsley{2%tbsp} together.
|
||||
|
||||
In a #medium bowl{}, combine the herbs, @red wine vinegar{1,1/2%tbsp}, @garlic{1/2%tsp}, jalapeño to taste, and @olive oil{3%tbsp}. Stir in the diced peach and season to taste with @salt{} and @pepper{}.
|
||||
|
||||
== Cook Chicken ==
|
||||
|
||||
In the same #large nonstick sauté pan{} used for squash, heat @olive oil{1%tbsp} over med-high. Pat @chicken breasts{2} dry and season with @salt{} and @pepper{}.
|
||||
|
||||
Add chicken to the pan and cook ~{6-9%minutes} per side, or until done. Remove, wipe out the pan, and keep warm.
|
||||
|
||||
== Make Batter & Cook Fritters ==
|
||||
|
||||
Line a #plate with a paper towel.
|
||||
|
||||
In a #large mixing bowl{}, mix @egg{1}, @olive oil{2%tbsp}, and @water{1/3%cup}. Add the Fritter Starter — @cornmeal{3/4%cup}, @flour{1/2%cup}, @sugar{2%tsp}, @baking powder{1%tsp}, and @table salt{1%tsp} — and stir to combine. Stir in the cooked squash and sliced green onion.
|
||||
|
||||
In the same #large nonstick sauté pan{} used for the chicken, heat @olive oil{2%tbsp} over medium. Scoop 1/4 cup batter into the heated oil to form fritters and cook ~{3-4%minutes} per side, working in batches and adding more oil as needed. Transfer to the paper-towel-lined plate.
|
||||
|
||||
Slice the cooked chicken.
|
||||
|
||||
== Garnish & Gather ==
|
||||
|
||||
Divide the squash fritters and sliced chicken between plates, then drizzle generously with the peach chimichurri.
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
title: Chicken Piccata
|
||||
author: Chef Torie Cox
|
||||
servings: 2
|
||||
time: 35 minutes
|
||||
prep time: 25 minutes
|
||||
cook time: 35 minutes
|
||||
description: With buttery angel hair pasta & dressed greens with parmesan
|
||||
diet:
|
||||
- contains dairy
|
||||
- contains wheat
|
||||
- contains egg
|
||||
source: /recipe-images/Scan_20260525_200616.jpg
|
||||
image: /recipe-images/chicken-piccata.png
|
||||
|
||||
------
|
||||
|
||||
== Make Piccata ==
|
||||
|
||||
Bring a #pot of salted @water to a boil for pasta.
|
||||
|
||||
Butterfly @chicken breasts{2} by placing a hand on top then slicing horizontally to divide in half, stopping about 1/2" from the opposite side so that it can open like a book.
|
||||
|
||||
Juice @lemon{1}.
|
||||
|
||||
Chop @parsley{1/4%c}.
|
||||
|
||||
Drain @capers{1%T}.
|
||||
|
||||
Heat @olive oil{2%T} or @olive oil{1%T} and @butter{2%T} in a #large sauté pan{} over med-high heat.
|
||||
|
||||
Add @chicken breasts{} and cook ~{3-4%minutes} per side or until done, then remove and set aside.
|
||||
|
||||
To the same #pan used for chicken, add @oil{1%tsp}, @garlic{1/2%t}, @capers{}, @salt{} & @pepper{}, then cook ~{30%seconds}.
|
||||
|
||||
Add @white wine{1/4%cup} and cook ~{2-3%minutes} or until liquid has mostly reduced.
|
||||
|
||||
Add @vegetable base{1%t}, @water{1/2%cup}, @lemon juice{} & @red pepper flakes{1/8%t}, then cook ~{4-6%minutes} or until slightly reduced.
|
||||
|
||||
Add @chicken breasts{} back to #pan, stirring to coat and warm through, ~{1-2%minutes}.
|
||||
|
||||
Remove from heat and stir in @parsley{}.
|
||||
|
||||
== Cook Pasta ==
|
||||
|
||||
Add @capellini{8%oz} to boiling @water{} and cook ~{3-5%minutes}, until al dente.
|
||||
|
||||
Drain and toss with @butter{1%T} or @olive oil{2%T}.
|
||||
|
||||
== Make Salad ==
|
||||
|
||||
In a #large bowl, whisk together @Dijon Starter{}, @olive oil{2%T}, @salt{} & @pepper{}.
|
||||
|
||||
Remove bottom root, if any, and roughly chop or tear @lettuce{2%c} into bite-sized pieces.
|
||||
|
||||
Add @lettuce{} to dressing, tossing to coat.
|
||||
|
||||
== Garnish & Gather ==
|
||||
|
||||
Divide @pasta{} evenly among plates or #shallow bowls{} and top with @chicken piccata{} & sauce. Serve @salad{} alongside and garnish @pasta{} & @salad{} with @parmesan{3%T}.
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
title: Chicken Primavera Bake
|
||||
image: /recipe-images/chicken-primavera-bake.png
|
||||
---
|
||||
|
||||
== Prep ==
|
||||
|
||||
Preheat oven to 425°F.
|
||||
|
||||
Slice @carrots{6%oz} into thin rounds; slice @button mushrooms{4%oz}. Mince @rosemary{2%tsp} and @chives{2%tsp} separately. Pat @chicken breasts{2} dry, cut into 1-inch pieces, and season with @salt{}.
|
||||
|
||||
In a #large bowl{}, whisk together @flour{3/4%cup}(for biscuits), @baking powder{1/2%tbsp}, @baking soda{1/2%tsp}, @sugar{1/2%tsp}, and @salt{1/4%tsp} to form the biscuit dry mix. Keep @butter{2%oz}(for biscuits) cold in the fridge until the biscuit step.
|
||||
|
||||
== Cook chicken & vegetables ==
|
||||
|
||||
Heat 1 tablespoon @olive oil{} in a #medium ovenproof sauté pan{} over medium-high heat. Add @chicken breasts{} and cook ~{3-4%minutes} until lightly browned; remove and set aside. Chicken will finish cooking in the oven.
|
||||
|
||||
Reduce heat to medium, add another tablespoon of @olive oil{}, then add @rosemary{}, @carrots{}, @button mushrooms{}, and @leeks{1/4%cup}(sliced). Cook ~{3-4%minutes} until softened; remove and set aside.
|
||||
|
||||
== Build the sauce ==
|
||||
|
||||
Add @white wine{1/4%cup} to the pan; cook ~{2-3%minutes} until mostly evaporated.
|
||||
|
||||
Add @butter{1%oz}(for sauce) and stir to melt. Add @flour{2%tbsp}(for sauce) and stir constantly ~{1-2%minutes} until incorporated.
|
||||
|
||||
Add @chicken broth{3/4%cup} and 1/2 cup water; stir ~{1-2%minutes} until thickened. Return chicken and vegetables to the pan.
|
||||
|
||||
Stir in @english peas{1/3%cup} and @mascarpone cheese{2%tbsp}. Season to taste with @salt{} and @pepper{}.
|
||||
|
||||
== Make biscuits & bake ==
|
||||
|
||||
Stir @chives{} into the biscuit dry mix. Remove cold @butter{2%oz}(for biscuits) from the fridge and grate directly into the bowl with a #box grater{}; toss to coat.
|
||||
|
||||
Stir in @ricotta cheese{6%tbsp} and 1/4 cup water with a fork, then knead a few times until no dry bits remain.
|
||||
|
||||
Pinch off tablespoon-sized pieces of dough, roll into balls, and arrange on top of the chicken mixture.
|
||||
|
||||
Bake ~{15-18%minutes} until biscuits are golden brown. Serve warm directly from the pan.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
title: Chicken with Caramelized Onion & Cardamom Rice
|
||||
source: Jerusalem - Ottolenghi
|
||||
servings: 4 servings
|
||||
tags: One Pot, Chicken, Dinner, Middle Eastern, Rice
|
||||
---
|
||||
|
||||
Put @sugar{3%tbsp} and water in a #small saucepan{} and heat until the sugar dissolves. Remove from the heat, add @barberries{2 1/2%tbsp}(or currants), and set aside to soak. If using currants, you do not need to soak them in this way.
|
||||
|
||||
Meanwhile, heat half the @olive oil{4%tbsp} in a #large sauté pan with lid{} over medium heat, add @onions{2}(medium, thinly sliced), and cook for ~{10-15%minutes}, stirring occasionally, until the onion has turned a deep golden brown. Transfer the onion to a small bowl and wipe the pan clean.
|
||||
|
||||
Place @chicken thighs{2 1/4%lb}(skin-on, bone-in, or 1 whole chicken quartered) in a #large mixing bowl{} and season with 1½ teaspoons each @salt{} and @black pepper{}. Add the remaining @olive oil{4%tbsp}, @cardamom pods{10}, @whole cloves{}, and @cinnamon sticks{2}(broken in two) and use your hands to mix everything together well. Heat the frying pan again and place the chicken and spices in it. Sear for ~{5%minutes} on each side and remove from the pan (this is important as it part-cooks the chicken). The spices can stay in the pan, but don't worry if they stick to the chicken. Remove most of the remaining oil as well, leaving just a thin film at the bottom. Add @basmati rice{1 2/3%cup}, the caramelized @onions{}, 1 teaspoon @salt{}, and plenty of @black pepper{}. Drain the barberries and add them as well. Stir well and return the seared chicken to the pan, pushing it into the rice.
|
||||
|
||||
Pour @boiling water{2 1/4%cup} over the rice and chicken, cover the pan, and cook over very low heat for ~{30%minutes}. Take the pan off the heat, remove the lid, quickly place a clean tea towel over the pan, and seal again with the lid. Leave the dish undisturbed for another ~{10%minutes}. Finally, add @parsley{1 1/2%tbsp}(chopped), @dill{1/2%cup}(chopped), and @cilantro{1/4%cup}(chopped) and use a fork to stir them in and fluff up the rice. Taste and add more salt and pepper if needed. Serve hot or warm with @Greek yogurt{1/3%cup}(mixed with 2 tbsp olive oil) if you like.
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
title: Chili Crisp Beef Lettuce Wraps
|
||||
subtitle: with crispy rice noodles & cucumber salad
|
||||
author: Chef Robin Pridgen
|
||||
servings: 2
|
||||
time: 35 minutes
|
||||
prep time: 30 minutes
|
||||
tags: [beef, lettuce wraps, asian, quick]
|
||||
allergens: [sesame, soy, wheat]
|
||||
image: /recipe-images/chili-crisp-beef-lettuce-wraps.png
|
||||
---
|
||||
|
||||
== Prep Veggies & Cook Rice ==
|
||||
|
||||
In a #pot, bring @water{1,1/2%cups} and @salt{} to a boil.
|
||||
|
||||
Thinly slice @radishes{1/4%c}.
|
||||
|
||||
Remove @cilantro{1/4%c} leaves from stems—do not chop.
|
||||
|
||||
Trim root from @bibb lettuce{1%head} and separate leaves.
|
||||
|
||||
Add @long grain rice{3/4%c} to boiling @water{}, return to a boil, then cover, reduce heat to low and cook ~{12-15%minutes}, until tender.
|
||||
|
||||
== Make Cucumber Salad ==
|
||||
|
||||
Peel @cucumber{2%c} then cut into 1/4" thick slices on the diagonal.
|
||||
|
||||
In a med #bowl, stir @tamari{1/2%T}, @sugar{1/2%T}, @rice vinegar{2%t}, @sesame oil{1%t} to combine -- Cucumber Dressing.
|
||||
|
||||
Add @cucumber{}, HALF of @cilantro{}, HALF of @black sesame seeds{1%t} & @salt{} to taste.
|
||||
|
||||
== Fry Rice Noodles ==
|
||||
|
||||
Break @rice vermicelli{1%oz} in half and separate the strands.
|
||||
|
||||
Line a #sheet pan or plate with @paper towels{}.
|
||||
|
||||
In a small #pot, heat @oil{1/2%cup} over med.
|
||||
|
||||
Test a few strands to make sure oil is hot enough, then working in 3-4 batches (6-8 batches), add a small handful of @rice noodles{} to hot @oil{}, turning with @tongs{}, until puffed—watch carefully, this will happen quickly!
|
||||
|
||||
Transfer @rice noodles{} to #paper towels to drain.
|
||||
|
||||
== Stir Fry Beef & Finish Rice ==
|
||||
|
||||
Stir @hoisin{2%T}, @tamari{1%T}, @honey{1%T}, @rice vinegar{1/4%T}, @garlic{1/4%T}, @diced ginger{1/4%T} to combine -- Beef Starter.
|
||||
|
||||
In a #sauté pan, heat @oil{1%Tbsp} over med-high.
|
||||
|
||||
Add @ground beef{12%oz}, season lightly with @salt{} & @pepper{}, and cook ~{4-5%minutes}, breaking up with a wooden spoon until browned, then spoon out excess juices.
|
||||
|
||||
Add @beef starter{} and cook another ~{2-3%minutes}.
|
||||
|
||||
To @rice{}, add remaining @black sesame seeds{} and fluff with a fork.
|
||||
|
||||
== Garnish & Gather ==
|
||||
|
||||
Serve @ground beef{} & @lettuce leaves{} with @radishes{}, remaining @cilantro{}, crispy @rice noodles{} & @chili crisp{1/4%c}. Enjoy @cucumber salad{} & @rice{} on the side.
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: Cilantro Lime Steak Fajitas
|
||||
subtitle: with peppers, onions, crema & cotija corn salad
|
||||
author: Chef Torie Cox
|
||||
servings: 2
|
||||
time: 25 minutes
|
||||
prep time: 20 minutes
|
||||
cuisine: Mexican
|
||||
tags:
|
||||
- steak
|
||||
- fajitas
|
||||
- mexican
|
||||
description: Cilantro lime steak fajitas with peppers, onions, crema, and cotija corn salad.
|
||||
source: /recipe-images/Scan_20260622_173831.jpg
|
||||
------
|
||||
|
||||
== Make Crema & Prep Veggies ==
|
||||
|
||||
Zest and juice the @lime{1}, keeping zest and juice separate. Chop @cilantro{1/4%cup}.
|
||||
|
||||
In a #medium bowl{}, stir together @sour cream{1/3%cup}, the lime zest, half of the cilantro, @salt, and @pepper to make the crema. Set aside.
|
||||
|
||||
Slice @red pepper{1}(thinly sliced). Slice @onion{1/3%cup}(thinly sliced). Shuck @corn{2%ears} then cut kernels from cobs. Thinly slice @radish{1/2%cup}.
|
||||
|
||||
In a separate #medium bowl{}, whisk together @olive oil{1%tbsp}, the lime juice, @salt{}, and @pepper{}. Add the corn, radish, @cotija cheese{2%tbsp}, and remaining cilantro to the lime dressing and toss to combine. Set corn salad aside.
|
||||
|
||||
== Cook Fajitas ==
|
||||
|
||||
In a #large sauté pan{}, heat @oil{1%tbsp} over med-high. Pat @sliced steak{10%oz} dry then season with half of the @fajita seasoning{} [- 1/2 tsp cumin, 1/2 tsp chili powder -] and @salt{}.
|
||||
|
||||
Add beef to hot pan and cook ~{3-5%minutes}, until browned and cooked to desired doneness, then remove from pan and set aside.
|
||||
|
||||
In the same #large sauté pan{}, heat another @oil{1/2%tbsp} over med-high. Add the peppers, onions, and remaining fajita seasoning, then cook ~{6-8%minutes} until tender.
|
||||
|
||||
== Finish Fajitas & Warm Tortillas ==
|
||||
|
||||
Return beef to the pan with the peppers and onions, toss to combine, then season to taste.
|
||||
|
||||
Wrap @flour tortillas{4} in a damp paper towel and microwave for ~{30%seconds} to warm.
|
||||
|
||||
== Garnish & Gather ==
|
||||
|
||||
Serve beef and veggies in tortillas topped with crema, and enjoy the corn salad on the side.
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,21 @@
|
||||
---
|
||||
title: Crispy Salmon & Cavatappi
|
||||
description: Crispy salmon and cavatappi with English peas and spring arugula pesto.
|
||||
servings: 2 servings
|
||||
tags: Pesto, Italian-American, Salmon, main course, Pasta
|
||||
image: /recipe-images/crispy-salmon-cavatappi.jpg
|
||||
---
|
||||
|
||||
Remove any large stems from @arugula{1 1/2%cup}. Remove woody stems from @mint{3%tbsp}. Zest and juice half of @lemon{1} and cut the remainder into wedges for garnish.
|
||||
|
||||
In a #food processor{} or #blender{}, pulse to combine @arugula{}, @parsley{1/4%cup}, @mint{}, @garlic clove{1}, @sunflower seeds{2%tbsp}, @red pepper flakes{1/8%tsp} (HOT!), and @pecorino cheese{2%tbsp}. Add lemon zest and juice from @lemon{}, then slowly add @olive oil{1/4%cup}. Process until smooth and season with @salt{} and @pepper{} to taste.
|
||||
|
||||
Bring a #large pot{} of salted water to a boil for @cavatappi{8%oz}.
|
||||
|
||||
Pat @salmon fillets{2} dry and season with @salt{} and @pepper{}. In a #large sauté pan{}, heat 1 tbsp @olive oil{1/4%cup} over medium-high heat. Add @salmon fillets{} skin-side down and cook until skin is crispy, about ~{5%minutes}. Flip salmon and cook another ~{2-3%minutes} or until desired doneness.
|
||||
|
||||
Add @cavatappi{} and @peas{1/2%cup} to the boiling water and cook until al dente, ~{3-5%minutes}, then drain, reserving 1/2 cup of the cooking water.
|
||||
|
||||
Return drained pasta to the pot along with most of the pesto, 1/3 cup reserved cooking water, and @butter{1%tbsp}, tossing until butter melts and sauce coats pasta well, adding additional pasta water as needed to loosen the sauce. Season to taste.
|
||||
|
||||
Divide pasta and salmon among shallow bowls or plates, drizzle with remaining pesto, and garnish with lemon wedges.
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
**Goal:** Give every `.cook` recipe file an `image:` field in YAML frontmatter pointing to `https://roux.roo.lol/recipe-images/<slug>.webp`, and download each image locally.
|
||||
|
||||
**Architecture:** Three parallel batches — (1) relink the 19 existing images, (2) discover + download + link 48 missing images (recipes with frontmatter), (3) create frontmatter + discover + link 9 more (recipes without frontmatter). Use subagent parallel dispatch for image discovery to keep wall-clock time manageable.
|
||||
**Architecture:** Three parallel batches — (1) relink the 19 existing images, (2) discover + download + link 50 missing images (recipes with frontmatter), (3) create frontmatter + discover + link 9 more (recipes without frontmatter). Use subagent parallel dispatch for image discovery to keep wall-clock time manageable.
|
||||
|
||||
**Tech Stack:** bash, curl, ImageMagick (convert), subagent researcher for web image search
|
||||
|
||||
@@ -13,50 +13,56 @@
|
||||
### Task 0: Setup — slug map, image dir, helper script
|
||||
|
||||
**Files:**
|
||||
- Create: `/home/jbrechtel/recipes/docs/slugs.csv`
|
||||
- Modify: none yet
|
||||
- Create: `docs/slugs.csv`
|
||||
- Create: `docs/has-image.csv`
|
||||
- Create: `docs/no-image-has-fm.csv`
|
||||
- Create: `docs/no-fm.csv`
|
||||
|
||||
- [ ] **Step 1: Generate slug mapping**
|
||||
|
||||
Run a one-liner that prints every `.cook` file with its slugified stem:
|
||||
Run a one-liner that prints every `.cook` file with its slugified stem, then deduplicate any collisions:
|
||||
|
||||
```bash
|
||||
cd /home/jbrechtel/recipes
|
||||
cd /home/jbrechtel/recipes/.worktrees/recipe-images
|
||||
for f in *.cook; do
|
||||
stem="${f%.cook}"
|
||||
slug=$(echo "$stem" \
|
||||
| tr '[:upper:]' '[:lower:]' \
|
||||
| sed -e 's/[^a-z0-9]/-/g' -e 's/--*/-/g' -e 's/^-//' -e 's/-$//')
|
||||
echo "$f|$slug"
|
||||
done > docs/slugs.csv
|
||||
head docs/slugs.csv
|
||||
done | awk -F'|' '
|
||||
{
|
||||
slug=$2
|
||||
count[slug]++
|
||||
if (count[slug] > 1) {
|
||||
slug = slug "-" count[slug]
|
||||
}
|
||||
print $1 "|" slug
|
||||
}' > docs/slugs.csv
|
||||
```
|
||||
|
||||
Expected: a pipe-delimited CSV like `Alain-Ducase-Bread.cook|alain-ducase-bread`.
|
||||
Total: 78 unique slugs (6 duplicate pairs get `-2` suffix).
|
||||
|
||||
- [ ] **Step 2: Create image directory**
|
||||
|
||||
```bash
|
||||
mkdir -p /home/jbrechtel/recipes/recipe-images
|
||||
mkdir -p recipe-images
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Classify each recipe**
|
||||
|
||||
```bash
|
||||
cd /home/jbrechtel/recipes
|
||||
echo "=== Already has image ==="
|
||||
cd /home/jbrechtel/recipes/.worktrees/recipe-images
|
||||
while IFS='|' read -r f slug; do
|
||||
if grep -q "^image:" "$f" 2>/dev/null; then echo "$f|$slug"; fi
|
||||
done < docs/slugs.csv > docs/has-image.csv
|
||||
|
||||
echo "=== Has frontmatter, no image ==="
|
||||
while IFS='|' read -r f slug; do
|
||||
if head -1 "$f" 2>/dev/null | grep -q "^---"; then
|
||||
if ! grep -q "^image:" "$f" 2>/dev/null; then echo "$f|$slug"; fi
|
||||
fi
|
||||
done < docs/slugs.csv > docs/no-image-has-fm.csv
|
||||
|
||||
echo "=== No frontmatter ==="
|
||||
while IFS='|' read -r f slug; do
|
||||
if ! head -1 "$f" 2>/dev/null | grep -q "^---"; then echo "$f|$slug"; fi
|
||||
done < docs/slugs.csv > docs/no-fm.csv
|
||||
@@ -64,12 +70,11 @@ done < docs/slugs.csv > docs/no-fm.csv
|
||||
wc -l docs/has-image.csv docs/no-image-has-fm.csv docs/no-fm.csv
|
||||
```
|
||||
|
||||
Expected: 19 / 48 / 9 lines respectively.
|
||||
Expected: 19 / 50 / 9 = 78 total.
|
||||
|
||||
- [ ] **Step 4: Commit setup**
|
||||
|
||||
```bash
|
||||
cd /home/jbrechtel/recipes
|
||||
git add docs/slugs.csv docs/has-image.csv docs/no-image-has-fm.csv docs/no-fm.csv
|
||||
git commit -m "chore: add recipe slug mapping and classification CSVs"
|
||||
```
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user