Phase 4: Recipe references + all 4 example file validation

- Fix pRecipeRef to include ./ prefix in stored path
- Add recipe reference test
- Add parse-validation tests for all 4 example .cook files
  (EasyPancakes, FriedRice, CoffeeSouffle, OlivierSalad)
- All 24 tests passing, all examples parse without error
This commit is contained in:
2026-05-18 22:59:44 -04:00
parent b5482d8785
commit c68d80e9c1
2 changed files with 44 additions and 1 deletions
+2 -1
View File
@@ -131,11 +131,12 @@ pLineBreak = do
pRecipeRef :: Parser StepItem pRecipeRef :: Parser StepItem
pRecipeRef = do pRecipeRef = do
_ <- P.char '@' _ <- P.char '@'
-- Consume @ and store ./ as part of the path
_ <- P.char '.' _ <- P.char '.'
_ <- P.char '/' _ <- P.char '/'
path <- P.many (P.noneOf "{(\n\\") path <- P.many (P.noneOf "{(\n\\")
mqty <- P.optionMaybe (P.between (P.char '{') (P.char '}') pQuantityBody) mqty <- P.optionMaybe (P.between (P.char '{') (P.char '}') pQuantityBody)
return (StepRecipeRef (RecipeRef path (join mqty))) return (StepRecipeRef (RecipeRef ("./" <> path) (join mqty)))
-- | Ingredient: @name{quantity%unit}(preparation) -- | Ingredient: @name{quantity%unit}(preparation)
pIngredient :: Parser StepItem pIngredient :: Parser StepItem
+42
View File
@@ -2,6 +2,7 @@ module Roux.ParserSpec (spec) where
import Data.List.NonEmpty (NonEmpty ((:|))) import Data.List.NonEmpty (NonEmpty ((:|)))
import qualified Data.List.NonEmpty as NE import qualified Data.List.NonEmpty as NE
import qualified Data.Text.IO as TIO
import Test.Hspec (Spec, describe, expectationFailure, it, shouldBe) import Test.Hspec (Spec, describe, expectationFailure, it, shouldBe)
import Roux.Parser (parseCookFile) import Roux.Parser (parseCookFile)
@@ -369,3 +370,44 @@ spec = do
_ -> _ ->
expectationFailure expectationFailure
("expected SecStep as third body item, got body=" <> show body) ("expected SecStep as third body item, got body=" <> show body)
describe "Recipe references" $ do
it "parses a recipe reference" $ do
let input = "Pour over with @./sauces/Hollandaise{150%g}."
case parseCookFile input of
Left err -> expectationFailure ("parse failed: " <> err)
Right recipe -> do
let items = stepItems recipe
let expected =
[ StepText "Pour over with "
, StepRecipeRef (RecipeRef "./sauces/Hollandaise" (Just (Quantity 150 (Just "g") False)))
, StepText "."
]
items `shouldBe` expected
describe "Full recipe files" $ do
let readExample name = do
content <- TIO.readFile ("cooklang-examples/" <> name <> ".cook")
case parseCookFile content of
Left err -> expectationFailure (name <> ": " <> err)
Right _ -> pure ()
it "EasyPancakes parses without error" $ do
readExample "EasyPancakes"
it "FriedRice parses without error" $ do
readExample "FriedRice"
it "CoffeeSouffle parses without error" $ do
readExample "CoffeeSouffle"
it "OlivierSalad parses without error" $ do
readExample "OlivierSalad"
-- | Extract step items from the first step of an unnamed section.
stepItems :: Recipe -> [StepItem]
stepItems r =
let s = NE.head (recipeSections r)
in case NE.head (sectionBody s) of
SecStep step -> unStep step
_ -> []