feat: add schemaOrgToCooklang conversion function

This commit is contained in:
2026-05-19 22:50:48 -04:00
parent 1addddd8fc
commit 3e55f0a7d2
2 changed files with 102 additions and 8 deletions
+38 -4
View File
@@ -1,7 +1,8 @@
module Roux.SchemaOrgSpec (spec) where
import qualified Data.Aeson as A
import Data.CookLang (Duration (..))
import Data.Aeson (Value, decodeStrict)
import Data.CookLang (Duration (..), Metadata (..), Recipe (..))
import Data.List.NonEmpty (toList)
import Data.Maybe (fromJust, isJust)
import Data.Text (Text)
import qualified Data.Text as T
@@ -11,14 +12,14 @@ import Test.Hspec (Spec, describe, expectationFailure, it, shouldBe, shouldSatis
import Roux.SchemaOrg
-- | Extract the first schema.org Recipe JSON-LD from HTML.
tryDecode :: Text -> Maybe A.Value
tryDecode :: Text -> Maybe Value
tryDecode html =
case T.splitOn "<script type=\"application/ld+json" html of
(_before : rest : _) ->
-- Skip past the closing > of the opening script tag, which may have extra attributes
let afterOpenTag = T.drop 1 (T.dropWhile (/= '>') rest)
in case T.splitOn "</script>" afterOpenTag of
(jsonContent : _) -> A.decodeStrict (TE.encodeUtf8 (T.strip jsonContent))
(jsonContent : _) -> decodeStrict (TE.encodeUtf8 (T.strip jsonContent))
_ -> Nothing
_ -> Nothing
@@ -63,3 +64,36 @@ spec = describe "SchemaOrg" $ do
it "PT30M -> 30 minutes" $ chk "PT30M" (Just (Duration 30 (Just "minutes")))
it "empty string -> Nothing" $ chk "" Nothing
it "human-readable -> Nothing" $ chk "20 minutes" Nothing
describe "schemaOrgToCooklang" $ do
it "converts fried-rice schema.org recipe to Cooklang" $ do
html <- readFile "test-data/fried-rice.html"
let decoded = tryDecode (T.pack html)
recipe = parseSchemaOrgRecipe (fromJust decoded)
case recipe of
Just r -> case schemaOrgToCooklang r of
Right cook -> do
metaTitle (recipeMetadata cook) `shouldBe` Just "Fried Rice"
metaSource (recipeMetadata cook) `shouldBe` Just "https://cooking.nytimes.com/recipes/12177-fried-rice"
metaCourse (recipeMetadata cook) `shouldBe` Just "one pot, side dish"
metaCuisine (recipeMetadata cook) `shouldBe` Just "asian"
metaTotalTime (recipeMetadata cook) `shouldBe` Just (Duration 20 (Just "minutes"))
metaTags (recipeMetadata cook) `shouldBe` ["egg", "rice", "vegetarian"]
metaDescription (recipeMetadata cook) `shouldSatisfy` isJust
let sections = toList (recipeSections cook)
length sections `shouldBe` 2
Left err -> fail err
Nothing -> fail "Failed to parse schema.org recipe"
it "converts carrot-risotto schema.org recipe to Cooklang" $ do
html <- readFile "test-data/carrot-risotto.html"
let decoded = tryDecode (T.pack html)
recipe = parseSchemaOrgRecipe (fromJust decoded)
case recipe of
Just r -> case schemaOrgToCooklang r of
Right cook -> do
metaTitle (recipeMetadata cook) `shouldBe` Just "Carrot Risotto With Chile Crisp"
metaAuthor (recipeMetadata cook) `shouldBe` Just "Alexa Weibel"
metaTags (recipeMetadata cook) `shouldSatisfy` (not . null)
Left err -> fail err
Nothing -> fail "Failed to parse schema.org recipe"