feat: add parseISODuration for ISO 8601 duration parsing
This commit is contained in:
+43
-2
@@ -12,6 +12,7 @@ module Roux.SchemaOrg (
|
|||||||
parseISODuration,
|
parseISODuration,
|
||||||
) where
|
) where
|
||||||
|
|
||||||
|
import Control.Monad (guard)
|
||||||
import Data.Aeson (FromJSON (..), (.!=), (.:), (.:?))
|
import Data.Aeson (FromJSON (..), (.!=), (.:), (.:?))
|
||||||
import qualified Data.Aeson as A
|
import qualified Data.Aeson as A
|
||||||
import qualified Data.Aeson.KeyMap as KM
|
import qualified Data.Aeson.KeyMap as KM
|
||||||
@@ -219,6 +220,46 @@ parseSchemaOrgRecipe val = do
|
|||||||
schemaOrgToCooklang :: SchemaOrgRecipe -> Either String Recipe
|
schemaOrgToCooklang :: SchemaOrgRecipe -> Either String Recipe
|
||||||
schemaOrgToCooklang _ = Left "Not yet implemented"
|
schemaOrgToCooklang _ = Left "Not yet implemented"
|
||||||
|
|
||||||
-- | Parse an ISO 8601 duration string. (Stub — will be implemented in Task 2)
|
{- | Parse an ISO 8601 duration string into a Cooklang Duration.
|
||||||
|
Supports formats like PT20M, PT1H30M, P1DT2H, P1D.
|
||||||
|
-}
|
||||||
parseISODuration :: Text -> Maybe Duration
|
parseISODuration :: Text -> Maybe Duration
|
||||||
parseISODuration _ = Nothing
|
parseISODuration t
|
||||||
|
| not ("P" `T.isPrefixOf` t) = Nothing
|
||||||
|
| otherwise = do
|
||||||
|
let rest = T.drop 1 t -- drop the P
|
||||||
|
(datePart, afterDate) = T.break (== 'T') rest
|
||||||
|
days = parseDateDays datePart
|
||||||
|
timePart = T.drop 1 afterDate -- drop the T
|
||||||
|
(hours, minutes, _seconds) = parseTimeComponent timePart
|
||||||
|
totalMinutes = days * 24 * 60 + hours * 60 + minutes
|
||||||
|
guard (totalMinutes > 0)
|
||||||
|
Just (Duration (toRational totalMinutes) (Just "minutes"))
|
||||||
|
where
|
||||||
|
parseDateDays :: Text -> Int
|
||||||
|
parseDateDays dp =
|
||||||
|
let (numStr, after) = T.break (== 'D') dp
|
||||||
|
in case T.uncons after of
|
||||||
|
Just ('D', _) -> fromMaybe 0 (readMaybe (T.unpack numStr))
|
||||||
|
_ -> 0
|
||||||
|
|
||||||
|
parseTimeComponent :: Text -> (Int, Int, Int)
|
||||||
|
parseTimeComponent tc =
|
||||||
|
let (hours, rest1) = parseUnit "H" tc
|
||||||
|
(minutes, rest2) = parseUnit "M" rest1
|
||||||
|
(seconds, _rest3) = parseUnit "S" rest2
|
||||||
|
in (hours, minutes, seconds)
|
||||||
|
|
||||||
|
parseUnit :: Text -> Text -> (Int, Text)
|
||||||
|
parseUnit unit tc =
|
||||||
|
let (numStr, after) = T.break (\c -> c == 'H' || c == 'M' || c == 'S') tc
|
||||||
|
in case T.uncons after of
|
||||||
|
Just (c, rest)
|
||||||
|
| T.singleton c == unit ->
|
||||||
|
(fromMaybe 0 (readMaybe (T.unpack numStr)), rest)
|
||||||
|
_ -> (0, tc)
|
||||||
|
|
||||||
|
readMaybe :: (Read a) => String -> Maybe a
|
||||||
|
readMaybe s = case reads s of
|
||||||
|
[(x, "")] -> Just x
|
||||||
|
_ -> Nothing
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
module Roux.SchemaOrgSpec (spec) where
|
module Roux.SchemaOrgSpec (spec) where
|
||||||
|
|
||||||
import qualified Data.Aeson as A
|
import qualified Data.Aeson as A
|
||||||
|
import Data.CookLang (Duration (..))
|
||||||
import Data.Maybe (fromJust, isJust)
|
import Data.Maybe (fromJust, isJust)
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
@@ -49,3 +50,16 @@ spec = describe "SchemaOrg" $ do
|
|||||||
case recipe of
|
case recipe of
|
||||||
Nothing -> expectationFailure "expected Just SchemaOrgRecipe, got Nothing"
|
Nothing -> expectationFailure "expected Just SchemaOrgRecipe, got Nothing"
|
||||||
Just r -> soName r `shouldBe` "Carrot Risotto With Chile Crisp"
|
Just r -> soName r `shouldBe` "Carrot Risotto With Chile Crisp"
|
||||||
|
|
||||||
|
describe "parseISODuration" $ do
|
||||||
|
let chk :: Text -> Maybe Duration -> IO ()
|
||||||
|
chk input expected = parseISODuration input `shouldBe` expected
|
||||||
|
|
||||||
|
it "PT20M -> 20 minutes" $ chk "PT20M" (Just (Duration 20 (Just "minutes")))
|
||||||
|
it "PT1H -> 60 minutes" $ chk "PT1H" (Just (Duration 60 (Just "minutes")))
|
||||||
|
it "PT1H30M -> 90 minutes" $ chk "PT1H30M" (Just (Duration 90 (Just "minutes")))
|
||||||
|
it "P1D -> 1440 minutes" $ chk "P1D" (Just (Duration 1440 (Just "minutes")))
|
||||||
|
it "P1DT2H -> 1560 minutes" $ chk "P1DT2H" (Just (Duration 1560 (Just "minutes")))
|
||||||
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user