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
|
license: BSD-3-Clause
|
||||||
|
|
||||||
default-extensions:
|
default-extensions:
|
||||||
|
- DerivingStrategies
|
||||||
- OverloadedStrings
|
- OverloadedStrings
|
||||||
|
|
||||||
ghc-options:
|
ghc-options:
|
||||||
@@ -28,6 +29,7 @@ dependencies:
|
|||||||
- blaze-html
|
- blaze-html
|
||||||
- blaze-markup
|
- blaze-markup
|
||||||
- bytestring
|
- bytestring
|
||||||
|
- containers
|
||||||
- directory
|
- directory
|
||||||
- http-types
|
- http-types
|
||||||
- optparse-applicative
|
- optparse-applicative
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ library
|
|||||||
hs-source-dirs:
|
hs-source-dirs:
|
||||||
src
|
src
|
||||||
default-extensions:
|
default-extensions:
|
||||||
|
DerivingStrategies
|
||||||
OverloadedStrings
|
OverloadedStrings
|
||||||
ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
|
ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
|
||||||
build-depends:
|
build-depends:
|
||||||
@@ -32,6 +33,7 @@ library
|
|||||||
, blaze-html
|
, blaze-html
|
||||||
, blaze-markup
|
, blaze-markup
|
||||||
, bytestring
|
, bytestring
|
||||||
|
, containers
|
||||||
, directory
|
, directory
|
||||||
, http-types
|
, http-types
|
||||||
, optparse-applicative
|
, optparse-applicative
|
||||||
@@ -51,6 +53,7 @@ executable roux-server
|
|||||||
hs-source-dirs:
|
hs-source-dirs:
|
||||||
app
|
app
|
||||||
default-extensions:
|
default-extensions:
|
||||||
|
DerivingStrategies
|
||||||
OverloadedStrings
|
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
|
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:
|
build-depends:
|
||||||
@@ -58,6 +61,7 @@ executable roux-server
|
|||||||
, blaze-html
|
, blaze-html
|
||||||
, blaze-markup
|
, blaze-markup
|
||||||
, bytestring
|
, bytestring
|
||||||
|
, containers
|
||||||
, directory
|
, directory
|
||||||
, http-types
|
, http-types
|
||||||
, optparse-applicative
|
, optparse-applicative
|
||||||
@@ -80,6 +84,7 @@ test-suite roux-server-test
|
|||||||
hs-source-dirs:
|
hs-source-dirs:
|
||||||
test
|
test
|
||||||
default-extensions:
|
default-extensions:
|
||||||
|
DerivingStrategies
|
||||||
OverloadedStrings
|
OverloadedStrings
|
||||||
ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
|
ghc-options: -Wall -Werror -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints
|
||||||
build-depends:
|
build-depends:
|
||||||
@@ -87,6 +92,7 @@ test-suite roux-server-test
|
|||||||
, blaze-html
|
, blaze-html
|
||||||
, blaze-markup
|
, blaze-markup
|
||||||
, bytestring
|
, bytestring
|
||||||
|
, containers
|
||||||
, directory
|
, directory
|
||||||
, http-types
|
, http-types
|
||||||
, optparse-applicative
|
, 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
|
module Roux.Types
|
||||||
( -- * Types
|
( -- * Recipe
|
||||||
RecipeDir (..)
|
Recipe (..)
|
||||||
|
|
||||||
|
-- * Sections and steps
|
||||||
|
, Section (..)
|
||||||
|
, Step (..)
|
||||||
|
|
||||||
|
-- * Inline elements
|
||||||
|
, StepItem (..)
|
||||||
|
, Ingredient (..)
|
||||||
|
, Cookware (..)
|
||||||
|
, Timer (..)
|
||||||
|
, RecipeRef (..)
|
||||||
|
|
||||||
|
-- * Quantities and durations
|
||||||
|
, Quantity (..)
|
||||||
|
, Duration (..)
|
||||||
|
|
||||||
|
-- * Metadata
|
||||||
|
, Metadata (..)
|
||||||
) where
|
) where
|
||||||
|
|
||||||
-- | Path to the directory containing Cooklang recipe files.
|
import Data.List.NonEmpty (NonEmpty)
|
||||||
newtype RecipeDir = RecipeDir FilePath
|
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