Add minimal Cooklang parser with first tests

- Create Roux.Parser with parseCookFile (splits paragraphs, treats
  each as a plain-text step, no metadata parsing yet)
- Add emptyMetadata to Roux.Types for convenience
- Add test dependencies: hspec, tasty-golden, tasty-hspec
- Add ParserSpec with 4 tests covering single step, multiple steps,
  empty input, and whitespace trimming
- Wire ParserSpec into test runner via tasty-hspec
This commit is contained in:
2026-05-18 22:21:32 -04:00
parent e907a03b09
commit 235adce596
6 changed files with 149 additions and 2 deletions
+39
View File
@@ -0,0 +1,39 @@
{- | Parser for Cooklang (.cook) recipe files.
Currently a minimal placeholder that splits input into paragraphs and
treats them as plain-text steps. Proper inline parsing (ingredients,
cookware, timers, …) and metadata support will be added incrementally.
-}
module Roux.Parser
( parseCookFile
) where
import Data.List.NonEmpty (NonEmpty ((:|)))
import Data.Text (Text)
import qualified Data.Text as T
import Roux.Types
-- | Parse a complete @.cook@ file into a 'Recipe'.
--
-- Returns 'Left' with an error message on invalid input (currently never
-- — the placeholder always succeeds).
parseCookFile :: Text -> Either String Recipe
parseCookFile input =
let paragraphs = filter (not . T.null) (map T.strip (T.splitOn "\n\n" (T.strip input)))
in case paragraphs of
[] ->
Right Recipe
{ recipeMetadata = emptyMetadata
, recipeSections = Section Nothing (Step [] :| []) :| []
}
(p : ps) ->
Right Recipe
{ recipeMetadata = emptyMetadata
, recipeSections = Section Nothing (stepFromText p :| fmap stepFromText ps) :| []
}
-- | Turn a paragraph of text into a single 'Step' containing nothing but
-- 'StepText'. This is a placeholder until inline parsing is implemented.
stepFromText :: Text -> Step
stepFromText t = Step [StepText t]