Define Cooklang data model and add DerivingStrategies + containers deps
- Add full Cooklang (.cook) recipe data model to Roux.Types: Recipe, Section, Step, StepItem (with StepText/StepIngredient/ StepCookware/StepTimer/StepRecipeRef/StepNote/StepComment/StepBreak), Ingredient, Cookware, Timer, RecipeRef, Quantity, Duration, Metadata - Enable DerivingStrategies extension project-wide - Add containers to build dependencies
This commit is contained in:
@@ -9,6 +9,7 @@ copyright: 2026 James Brechtel
|
||||
license: BSD-3-Clause
|
||||
|
||||
default-extensions:
|
||||
- DerivingStrategies
|
||||
- OverloadedStrings
|
||||
|
||||
ghc-options:
|
||||
@@ -28,6 +29,7 @@ dependencies:
|
||||
- blaze-html
|
||||
- blaze-markup
|
||||
- bytestring
|
||||
- containers
|
||||
- directory
|
||||
- http-types
|
||||
- optparse-applicative
|
||||
|
||||
@@ -25,6 +25,7 @@ library
|
||||
hs-source-dirs:
|
||||
src
|
||||
default-extensions:
|
||||
DerivingStrategies
|
||||
OverloadedStrings
|
||||
ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
|
||||
build-depends:
|
||||
@@ -32,6 +33,7 @@ library
|
||||
, blaze-html
|
||||
, blaze-markup
|
||||
, bytestring
|
||||
, containers
|
||||
, directory
|
||||
, http-types
|
||||
, optparse-applicative
|
||||
@@ -51,6 +53,7 @@ executable roux-server
|
||||
hs-source-dirs:
|
||||
app
|
||||
default-extensions:
|
||||
DerivingStrategies
|
||||
OverloadedStrings
|
||||
ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N
|
||||
build-depends:
|
||||
@@ -58,6 +61,7 @@ executable roux-server
|
||||
, blaze-html
|
||||
, blaze-markup
|
||||
, bytestring
|
||||
, containers
|
||||
, directory
|
||||
, http-types
|
||||
, optparse-applicative
|
||||
@@ -80,6 +84,7 @@ test-suite roux-server-test
|
||||
hs-source-dirs:
|
||||
test
|
||||
default-extensions:
|
||||
DerivingStrategies
|
||||
OverloadedStrings
|
||||
ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
|
||||
build-depends:
|
||||
@@ -87,6 +92,7 @@ test-suite roux-server-test
|
||||
, blaze-html
|
||||
, blaze-markup
|
||||
, bytestring
|
||||
, containers
|
||||
, directory
|
||||
, http-types
|
||||
, optparse-applicative
|
||||
|
||||
+181
-5
@@ -1,9 +1,185 @@
|
||||
{- | Core types for Roux.
|
||||
{- | Core types for Roux. Models the structure of a parsed Cooklang (.cook)
|
||||
recipe file as specified at <https://cooklang.org/docs/spec/>.
|
||||
|
||||
Hierarchy:
|
||||
|
||||
@
|
||||
Recipe
|
||||
Metadata — YAML front matter (title, tags, servings, …)
|
||||
NonEmpty Section — at least one section per recipe
|
||||
|
||||
Section
|
||||
Maybe Text — optional section name ("Dough", "Filling")
|
||||
NonEmpty Step — at least one step per section
|
||||
|
||||
Step
|
||||
[StepItem] — sequence of inline elements
|
||||
|
||||
StepItem
|
||||
StepText — plain text
|
||||
StepIngredient — \@name{amount%unit}(preparation)
|
||||
StepCookware — \#name{}
|
||||
StepTimer — \~name{duration%unit}
|
||||
StepRecipeRef — \@.\/path\/to\/recipe{amount}
|
||||
StepNote — \> note / commentary
|
||||
StepComment — [- comment -]
|
||||
StepBreak — \\ at end of line
|
||||
@
|
||||
-}
|
||||
module Roux.Types
|
||||
( -- * Types
|
||||
RecipeDir (..)
|
||||
( -- * Recipe
|
||||
Recipe (..)
|
||||
|
||||
-- * Sections and steps
|
||||
, Section (..)
|
||||
, Step (..)
|
||||
|
||||
-- * Inline elements
|
||||
, StepItem (..)
|
||||
, Ingredient (..)
|
||||
, Cookware (..)
|
||||
, Timer (..)
|
||||
, RecipeRef (..)
|
||||
|
||||
-- * Quantities and durations
|
||||
, Quantity (..)
|
||||
, Duration (..)
|
||||
|
||||
-- * Metadata
|
||||
, Metadata (..)
|
||||
) where
|
||||
|
||||
-- | Path to the directory containing Cooklang recipe files.
|
||||
newtype RecipeDir = RecipeDir FilePath
|
||||
import Data.List.NonEmpty (NonEmpty)
|
||||
import Data.Map.Strict (Map)
|
||||
import Data.Text (Text)
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Top-level
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- | A fully parsed Cooklang recipe file.
|
||||
data Recipe = Recipe
|
||||
{ recipeMetadata :: !Metadata
|
||||
, recipeSections :: !(NonEmpty Section)
|
||||
}
|
||||
deriving stock (Eq, Show)
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Sections and steps
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- | A named or unnamed section within a recipe. Each section contains one
|
||||
-- or more cooking steps.
|
||||
data Section = Section
|
||||
{ sectionName :: !(Maybe Text)
|
||||
, sectionSteps :: !(NonEmpty Step)
|
||||
}
|
||||
deriving stock (Eq, Show)
|
||||
|
||||
-- | A single cooking step — one paragraph of text in the Cooklang file,
|
||||
-- separated from other steps by a blank line.
|
||||
newtype Step = Step
|
||||
{ unStep :: [StepItem]
|
||||
}
|
||||
deriving stock (Eq, Show)
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Inline elements
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- | An element that appears within a step.
|
||||
data StepItem
|
||||
= StepText !Text
|
||||
| StepIngredient !Ingredient
|
||||
| StepCookware !Cookware
|
||||
| StepTimer !Timer
|
||||
| StepRecipeRef !RecipeRef
|
||||
| StepNote !Text
|
||||
| StepComment !Text
|
||||
| StepBreak
|
||||
deriving stock (Eq, Show)
|
||||
|
||||
-- | An ingredient reference.
|
||||
--
|
||||
-- @\@salt\@ground black pepper{}\@potato{2}\@bacon strips{1%kg}@
|
||||
data Ingredient = Ingredient
|
||||
{ ingName :: !Text
|
||||
, ingQuantity :: !(Maybe Quantity)
|
||||
, ingPreparation :: !(Maybe Text)
|
||||
}
|
||||
deriving stock (Eq, Show)
|
||||
|
||||
-- | A piece of cookware.
|
||||
--
|
||||
-- @\#pot\#potato masher{}@
|
||||
newtype Cookware = Cookware
|
||||
{ cwName :: Text
|
||||
}
|
||||
deriving stock (Eq, Show)
|
||||
|
||||
-- | A timer.
|
||||
--
|
||||
-- @\{25%minutes\}\~eggs{3%minutes}@
|
||||
data Timer = Timer
|
||||
{ timerName :: !(Maybe Text)
|
||||
, timerDuration :: !Duration
|
||||
}
|
||||
deriving stock (Eq, Show)
|
||||
|
||||
-- | A reference to another recipe, signalled by a path starting with @.\/@.
|
||||
--
|
||||
-- @\@.\/sauces\/Hollandaise{150%g}@
|
||||
data RecipeRef = RecipeRef
|
||||
{ refPath :: !FilePath
|
||||
, refQuantity :: !(Maybe Quantity)
|
||||
}
|
||||
deriving stock (Eq, Show)
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Quantities and durations
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- | An ingredient quantity with optional unit and scaling lock.
|
||||
--
|
||||
-- @{2}\{1%kg}\{1\/2%tbsp}\{=1%tsp}@
|
||||
data Quantity = Quantity
|
||||
{ quantityAmount :: !Rational -- ^ e.g. @1\/2@, @3@, @3\/4@
|
||||
, quantityUnit :: !(Maybe Text) -- ^ e.g. @\"kg\"@, @\"tbsp\"@
|
||||
, quantityFixed :: !Bool -- ^ @{=1%tsp}@ — locked from scaling
|
||||
}
|
||||
deriving stock (Eq, Show)
|
||||
|
||||
-- | A timer duration — structurally similar to 'Quantity' but semantically
|
||||
-- distinct. Timers are not scaled with serving size.
|
||||
data Duration = Duration
|
||||
{ durationAmount :: !Rational -- ^ e.g. @25@, @3@
|
||||
, durationUnit :: !(Maybe Text) -- ^ e.g. @\"minutes\"@, @\"hours\"@
|
||||
}
|
||||
deriving stock (Eq, Show)
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- Metadata
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
-- | Metadata parsed from YAML front matter (the @---@ … @---@ block at the
|
||||
-- top of a @.cook@ file).
|
||||
--
|
||||
-- Canonical metadata keys are lifted to dedicated fields; everything else
|
||||
-- lives in 'metaCustom'.
|
||||
data Metadata = Metadata
|
||||
{ metaTitle :: !(Maybe Text)
|
||||
, metaServings :: !(Maybe (Int, Maybe Text))
|
||||
, metaAuthor :: !(Maybe Text)
|
||||
, metaSource :: !(Maybe Text)
|
||||
, metaTotalTime :: !(Maybe Duration)
|
||||
, metaPrepTime :: !(Maybe Duration)
|
||||
, metaCookTime :: !(Maybe Duration)
|
||||
, metaDifficulty :: !(Maybe Text)
|
||||
, metaCuisine :: !(Maybe Text)
|
||||
, metaDiet :: ![Text]
|
||||
, metaTags :: ![Text]
|
||||
, metaImage :: !(Maybe Text)
|
||||
, metaDescription :: !(Maybe Text)
|
||||
, metaCustom :: !(Map Text Text)
|
||||
}
|
||||
deriving stock (Eq, Show)
|
||||
|
||||
Reference in New Issue
Block a user