Add fourmolu + hlint with scripts and git hooks (Atlas-style)
- Create scripts/build, scripts/test, scripts/run, scripts/install-hooks - Create git-hooks/pre-commit (auto-format staged .hs files) - Create git-hooks/pre-push (check formatting + hlint before push) - Handle git worktrees correctly in hook installation - Run fourmolu on all source files to fix existing formatting
This commit is contained in:
Executable
+12
@@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
||||||
|
|
||||||
|
echo "Formatting with fourmolu..."
|
||||||
|
./hs fourmolu --mode inplace app/ src/ test/
|
||||||
|
|
||||||
|
echo "Linting with hlint..."
|
||||||
|
./hs hlint app/ src/ test/
|
||||||
|
|
||||||
|
echo "Building..."
|
||||||
|
./hs stack build --copy-bins --local-bin-path /work/build
|
||||||
Executable
+34
@@ -0,0 +1,34 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Pre-commit hook: format staged Haskell files with fourmolu.
|
||||||
|
# Installed by: scripts/install-hooks
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
|
# Get staged .hs files (handles filenames with spaces)
|
||||||
|
staged=$(git -C "$PROJECT_DIR" diff --cached --name-only --diff-filter=ACM -- '*.hs')
|
||||||
|
if [ -z "$staged" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect the files into an array
|
||||||
|
mapfile -t files <<< "$staged"
|
||||||
|
|
||||||
|
echo "--- Formatting staged Haskell files with fourmolu..."
|
||||||
|
if ! "$PROJECT_DIR/hs" fourmolu --mode inplace "${files[@]}"; then
|
||||||
|
echo "ERROR: fourmolu formatting failed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Re-stage any files that were modified by formatting
|
||||||
|
changed=0
|
||||||
|
for f in "${files[@]}"; do
|
||||||
|
if ! git -C "$PROJECT_DIR" diff --quiet "$f"; then
|
||||||
|
git -C "$PROJECT_DIR" add "$f"
|
||||||
|
changed=$((changed + 1))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$changed" -gt 0 ]; then
|
||||||
|
echo "--- Formatted $changed file(s) — re-staged automatically"
|
||||||
|
fi
|
||||||
Executable
+23
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Pre-push hook: check formatting and hlint before pushing.
|
||||||
|
# Installed by: scripts/install-hooks
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PROJECT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
|
||||||
|
echo "--- Checking formatting with fourmolu..."
|
||||||
|
if ! "$PROJECT_DIR/hs" fourmolu --mode check app/ src/ test/; then
|
||||||
|
echo ""
|
||||||
|
echo "ERROR: Formatting check failed. Run './scripts/build' to auto-format."
|
||||||
|
echo " (fourmolu --mode inplace app/ src/ test/)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "OK"
|
||||||
|
|
||||||
|
echo "--- Linting with hlint..."
|
||||||
|
if ! "$PROJECT_DIR/hs" hlint app/ src/ test/; then
|
||||||
|
echo ""
|
||||||
|
echo "ERROR: Hlint found warnings. Fix them before pushing."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "OK"
|
||||||
Executable
+24
@@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Install git hooks for the roux-server project.
|
||||||
|
# Run once: ./scripts/install-hooks
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
HOOK_SRC="$(cd "$(dirname "${BASH_SOURCE[0]}")/git-hooks" && pwd)"
|
||||||
|
# Determine git hooks directory — supports both regular repos and worktrees
|
||||||
|
GIT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/.git"
|
||||||
|
if [ -f "$GIT_DIR" ]; then
|
||||||
|
# Worktree: .git is a file pointing to the real gitdir
|
||||||
|
GIT_DIR="$(cut -d' ' -f2 "$GIT_DIR")"
|
||||||
|
fi
|
||||||
|
HOOK_DST="${GIT_DIR}/hooks"
|
||||||
|
|
||||||
|
mkdir -p "$HOOK_DST"
|
||||||
|
echo "Installing git hooks from $HOOK_SRC -> $HOOK_DST"
|
||||||
|
|
||||||
|
for hook in "$HOOK_SRC"/*; do
|
||||||
|
name=$(basename "$hook")
|
||||||
|
ln -sf "$hook" "$HOOK_DST/$name"
|
||||||
|
echo " $name installed"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Done."
|
||||||
Executable
+4
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
||||||
|
./hs stack exec roux-server -- "$@"
|
||||||
Executable
+12
@@ -0,0 +1,12 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
||||||
|
|
||||||
|
echo "Checking formatting with fourmolu..."
|
||||||
|
./hs fourmolu --mode check app/ src/ test/
|
||||||
|
|
||||||
|
echo "Linting with hlint..."
|
||||||
|
./hs hlint app/ src/ test/
|
||||||
|
|
||||||
|
echo "Running tests..."
|
||||||
|
./hs stack test
|
||||||
+5
-6
@@ -1,9 +1,8 @@
|
|||||||
{- | Top-level re-exports and application setup for Roux.
|
-- | Top-level re-exports and application setup for Roux.
|
||||||
-}
|
module Roux (
|
||||||
module Roux
|
app,
|
||||||
( app
|
module X,
|
||||||
, module X
|
) where
|
||||||
) where
|
|
||||||
|
|
||||||
import qualified Network.HTTP.Types as HTTP
|
import qualified Network.HTTP.Types as HTTP
|
||||||
import qualified Network.Wai as Wai
|
import qualified Network.Wai as Wai
|
||||||
|
|||||||
+21
-17
@@ -4,9 +4,9 @@ Currently a minimal placeholder that splits input into paragraphs and
|
|||||||
treats them as plain-text steps. Proper inline parsing (ingredients,
|
treats them as plain-text steps. Proper inline parsing (ingredients,
|
||||||
cookware, timers, …) and metadata support will be added incrementally.
|
cookware, timers, …) and metadata support will be added incrementally.
|
||||||
-}
|
-}
|
||||||
module Roux.Parser
|
module Roux.Parser (
|
||||||
( parseCookFile
|
parseCookFile,
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.List.NonEmpty (NonEmpty ((:|)))
|
import Data.List.NonEmpty (NonEmpty ((:|)))
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
@@ -14,26 +14,30 @@ import qualified Data.Text as T
|
|||||||
|
|
||||||
import Roux.Types
|
import Roux.Types
|
||||||
|
|
||||||
-- | Parse a complete @.cook@ file into a 'Recipe'.
|
{- | Parse a complete @.cook@ file into a 'Recipe'.
|
||||||
--
|
|
||||||
-- Returns 'Left' with an error message on invalid input (currently never
|
Returns 'Left' with an error message on invalid input (currently never
|
||||||
-- — the placeholder always succeeds).
|
— the placeholder always succeeds).
|
||||||
|
-}
|
||||||
parseCookFile :: Text -> Either String Recipe
|
parseCookFile :: Text -> Either String Recipe
|
||||||
parseCookFile input =
|
parseCookFile input =
|
||||||
let paragraphs = filter (not . T.null) (map T.strip (T.splitOn "\n\n" (T.strip input)))
|
let paragraphs = filter (not . T.null) (map T.strip (T.splitOn "\n\n" (T.strip input)))
|
||||||
in case paragraphs of
|
in case paragraphs of
|
||||||
[] ->
|
[] ->
|
||||||
Right Recipe
|
Right
|
||||||
{ recipeMetadata = emptyMetadata
|
Recipe
|
||||||
, recipeSections = Section Nothing (Step [] :| []) :| []
|
{ recipeMetadata = emptyMetadata
|
||||||
}
|
, recipeSections = Section Nothing (Step [] :| []) :| []
|
||||||
|
}
|
||||||
(p : ps) ->
|
(p : ps) ->
|
||||||
Right Recipe
|
Right
|
||||||
{ recipeMetadata = emptyMetadata
|
Recipe
|
||||||
, recipeSections = Section Nothing (stepFromText p :| fmap stepFromText ps) :| []
|
{ recipeMetadata = emptyMetadata
|
||||||
}
|
, recipeSections = Section Nothing (stepFromText p :| fmap stepFromText ps) :| []
|
||||||
|
}
|
||||||
|
|
||||||
-- | Turn a paragraph of text into a single 'Step' containing nothing but
|
{- | Turn a paragraph of text into a single 'Step' containing nothing but
|
||||||
-- 'StepText'. This is a placeholder until inline parsing is implemented.
|
'StepText'. This is a placeholder until inline parsing is implemented.
|
||||||
|
-}
|
||||||
stepFromText :: Text -> Step
|
stepFromText :: Text -> Step
|
||||||
stepFromText t = Step [StepText t]
|
stepFromText t = Step [StepText t]
|
||||||
|
|||||||
+95
-81
@@ -26,29 +26,29 @@ StepItem
|
|||||||
StepBreak — \\ at end of line
|
StepBreak — \\ at end of line
|
||||||
@
|
@
|
||||||
-}
|
-}
|
||||||
module Roux.Types
|
module Roux.Types (
|
||||||
( -- * Recipe
|
-- * Recipe
|
||||||
Recipe (..)
|
Recipe (..),
|
||||||
|
|
||||||
-- * Sections and steps
|
-- * Sections and steps
|
||||||
, Section (..)
|
Section (..),
|
||||||
, Step (..)
|
Step (..),
|
||||||
|
|
||||||
-- * Inline elements
|
-- * Inline elements
|
||||||
, StepItem (..)
|
StepItem (..),
|
||||||
, Ingredient (..)
|
Ingredient (..),
|
||||||
, Cookware (..)
|
Cookware (..),
|
||||||
, Timer (..)
|
Timer (..),
|
||||||
, RecipeRef (..)
|
RecipeRef (..),
|
||||||
|
|
||||||
-- * Quantities and durations
|
-- * Quantities and durations
|
||||||
, Quantity (..)
|
Quantity (..),
|
||||||
, Duration (..)
|
Duration (..),
|
||||||
|
|
||||||
-- * Metadata
|
-- * Metadata
|
||||||
, Metadata (..)
|
Metadata (..),
|
||||||
, emptyMetadata
|
emptyMetadata,
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Data.List.NonEmpty (NonEmpty)
|
import Data.List.NonEmpty (NonEmpty)
|
||||||
import Data.Map.Strict (Map)
|
import Data.Map.Strict (Map)
|
||||||
@@ -70,16 +70,18 @@ data Recipe = Recipe
|
|||||||
-- Sections and steps
|
-- Sections and steps
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
-- | A named or unnamed section within a recipe. Each section contains one
|
{- | A named or unnamed section within a recipe. Each section contains one
|
||||||
-- or more cooking steps.
|
or more cooking steps.
|
||||||
|
-}
|
||||||
data Section = Section
|
data Section = Section
|
||||||
{ sectionName :: !(Maybe Text)
|
{ sectionName :: !(Maybe Text)
|
||||||
, sectionSteps :: !(NonEmpty Step)
|
, sectionSteps :: !(NonEmpty Step)
|
||||||
}
|
}
|
||||||
deriving stock (Eq, Show)
|
deriving stock (Eq, Show)
|
||||||
|
|
||||||
-- | A single cooking step — one paragraph of text in the Cooklang file,
|
{- | A single cooking step — one paragraph of text in the Cooklang file,
|
||||||
-- separated from other steps by a blank line.
|
separated from other steps by a blank line.
|
||||||
|
-}
|
||||||
newtype Step = Step
|
newtype Step = Step
|
||||||
{ unStep :: [StepItem]
|
{ unStep :: [StepItem]
|
||||||
}
|
}
|
||||||
@@ -101,38 +103,42 @@ data StepItem
|
|||||||
| StepBreak
|
| StepBreak
|
||||||
deriving stock (Eq, Show)
|
deriving stock (Eq, Show)
|
||||||
|
|
||||||
-- | An ingredient reference.
|
{- | An ingredient reference.
|
||||||
--
|
|
||||||
-- @\@salt\@ground black pepper{}\@potato{2}\@bacon strips{1%kg}@
|
@\@salt\@ground black pepper{}\@potato{2}\@bacon strips{1%kg}@
|
||||||
|
-}
|
||||||
data Ingredient = Ingredient
|
data Ingredient = Ingredient
|
||||||
{ ingName :: !Text
|
{ ingName :: !Text
|
||||||
, ingQuantity :: !(Maybe Quantity)
|
, ingQuantity :: !(Maybe Quantity)
|
||||||
, ingPreparation :: !(Maybe Text)
|
, ingPreparation :: !(Maybe Text)
|
||||||
}
|
}
|
||||||
deriving stock (Eq, Show)
|
deriving stock (Eq, Show)
|
||||||
|
|
||||||
-- | A piece of cookware.
|
{- | A piece of cookware.
|
||||||
--
|
|
||||||
-- @\#pot\#potato masher{}@
|
@\#pot\#potato masher{}@
|
||||||
|
-}
|
||||||
newtype Cookware = Cookware
|
newtype Cookware = Cookware
|
||||||
{ cwName :: Text
|
{ cwName :: Text
|
||||||
}
|
}
|
||||||
deriving stock (Eq, Show)
|
deriving stock (Eq, Show)
|
||||||
|
|
||||||
-- | A timer.
|
{- | A timer.
|
||||||
--
|
|
||||||
-- @\{25%minutes\}\~eggs{3%minutes}@
|
@\{25%minutes\}\~eggs{3%minutes}@
|
||||||
|
-}
|
||||||
data Timer = Timer
|
data Timer = Timer
|
||||||
{ timerName :: !(Maybe Text)
|
{ timerName :: !(Maybe Text)
|
||||||
, timerDuration :: !Duration
|
, timerDuration :: !Duration
|
||||||
}
|
}
|
||||||
deriving stock (Eq, Show)
|
deriving stock (Eq, Show)
|
||||||
|
|
||||||
-- | A reference to another recipe, signalled by a path starting with @.\/@.
|
{- | A reference to another recipe, signalled by a path starting with @.\/@.
|
||||||
--
|
|
||||||
-- @\@.\/sauces\/Hollandaise{150%g}@
|
@\@.\/sauces\/Hollandaise{150%g}@
|
||||||
|
-}
|
||||||
data RecipeRef = RecipeRef
|
data RecipeRef = RecipeRef
|
||||||
{ refPath :: !FilePath
|
{ refPath :: !FilePath
|
||||||
, refQuantity :: !(Maybe Quantity)
|
, refQuantity :: !(Maybe Quantity)
|
||||||
}
|
}
|
||||||
deriving stock (Eq, Show)
|
deriving stock (Eq, Show)
|
||||||
@@ -141,21 +147,28 @@ data RecipeRef = RecipeRef
|
|||||||
-- Quantities and durations
|
-- Quantities and durations
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
-- | An ingredient quantity with optional unit and scaling lock.
|
{- | An ingredient quantity with optional unit and scaling lock.
|
||||||
--
|
|
||||||
-- @{2}\{1%kg}\{1\/2%tbsp}\{=1%tsp}@
|
@{2}\{1%kg}\{1\/2%tbsp}\{=1%tsp}@
|
||||||
|
-}
|
||||||
data Quantity = Quantity
|
data Quantity = Quantity
|
||||||
{ quantityAmount :: !Rational -- ^ e.g. @1\/2@, @3@, @3\/4@
|
{ quantityAmount :: !Rational
|
||||||
, quantityUnit :: !(Maybe Text) -- ^ e.g. @\"kg\"@, @\"tbsp\"@
|
-- ^ e.g. @1\/2@, @3@, @3\/4@
|
||||||
, quantityFixed :: !Bool -- ^ @{=1%tsp}@ — locked from scaling
|
, quantityUnit :: !(Maybe Text)
|
||||||
|
-- ^ e.g. @\"kg\"@, @\"tbsp\"@
|
||||||
|
, quantityFixed :: !Bool
|
||||||
|
-- ^ @{=1%tsp}@ — locked from scaling
|
||||||
}
|
}
|
||||||
deriving stock (Eq, Show)
|
deriving stock (Eq, Show)
|
||||||
|
|
||||||
-- | A timer duration — structurally similar to 'Quantity' but semantically
|
{- | A timer duration — structurally similar to 'Quantity' but semantically
|
||||||
-- distinct. Timers are not scaled with serving size.
|
distinct. Timers are not scaled with serving size.
|
||||||
|
-}
|
||||||
data Duration = Duration
|
data Duration = Duration
|
||||||
{ durationAmount :: !Rational -- ^ e.g. @25@, @3@
|
{ durationAmount :: !Rational
|
||||||
, durationUnit :: !(Maybe Text) -- ^ e.g. @\"minutes\"@, @\"hours\"@
|
-- ^ e.g. @25@, @3@
|
||||||
|
, durationUnit :: !(Maybe Text)
|
||||||
|
-- ^ e.g. @\"minutes\"@, @\"hours\"@
|
||||||
}
|
}
|
||||||
deriving stock (Eq, Show)
|
deriving stock (Eq, Show)
|
||||||
|
|
||||||
@@ -163,26 +176,27 @@ data Duration = Duration
|
|||||||
-- Metadata
|
-- Metadata
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
|
|
||||||
-- | Metadata parsed from YAML front matter (the @---@ … @---@ block at the
|
{- | Metadata parsed from YAML front matter (the @---@ … @---@ block at the
|
||||||
-- top of a @.cook@ file).
|
top of a @.cook@ file).
|
||||||
--
|
|
||||||
-- Canonical metadata keys are lifted to dedicated fields; everything else
|
Canonical metadata keys are lifted to dedicated fields; everything else
|
||||||
-- lives in 'metaCustom'.
|
lives in 'metaCustom'.
|
||||||
|
-}
|
||||||
data Metadata = Metadata
|
data Metadata = Metadata
|
||||||
{ metaTitle :: !(Maybe Text)
|
{ metaTitle :: !(Maybe Text)
|
||||||
, metaServings :: !(Maybe (Int, Maybe Text))
|
, metaServings :: !(Maybe (Int, Maybe Text))
|
||||||
, metaAuthor :: !(Maybe Text)
|
, metaAuthor :: !(Maybe Text)
|
||||||
, metaSource :: !(Maybe Text)
|
, metaSource :: !(Maybe Text)
|
||||||
, metaTotalTime :: !(Maybe Duration)
|
, metaTotalTime :: !(Maybe Duration)
|
||||||
, metaPrepTime :: !(Maybe Duration)
|
, metaPrepTime :: !(Maybe Duration)
|
||||||
, metaCookTime :: !(Maybe Duration)
|
, metaCookTime :: !(Maybe Duration)
|
||||||
, metaDifficulty :: !(Maybe Text)
|
, metaDifficulty :: !(Maybe Text)
|
||||||
, metaCuisine :: !(Maybe Text)
|
, metaCuisine :: !(Maybe Text)
|
||||||
, metaDiet :: ![Text]
|
, metaDiet :: ![Text]
|
||||||
, metaTags :: ![Text]
|
, metaTags :: ![Text]
|
||||||
, metaImage :: !(Maybe Text)
|
, metaImage :: !(Maybe Text)
|
||||||
, metaDescription :: !(Maybe Text)
|
, metaDescription :: !(Maybe Text)
|
||||||
, metaCustom :: !(Map Text Text)
|
, metaCustom :: !(Map Text Text)
|
||||||
}
|
}
|
||||||
deriving stock (Eq, Show)
|
deriving stock (Eq, Show)
|
||||||
|
|
||||||
@@ -190,18 +204,18 @@ data Metadata = Metadata
|
|||||||
emptyMetadata :: Metadata
|
emptyMetadata :: Metadata
|
||||||
emptyMetadata =
|
emptyMetadata =
|
||||||
Metadata
|
Metadata
|
||||||
{ metaTitle = Nothing
|
{ metaTitle = Nothing
|
||||||
, metaServings = Nothing
|
, metaServings = Nothing
|
||||||
, metaAuthor = Nothing
|
, metaAuthor = Nothing
|
||||||
, metaSource = Nothing
|
, metaSource = Nothing
|
||||||
, metaTotalTime = Nothing
|
, metaTotalTime = Nothing
|
||||||
, metaPrepTime = Nothing
|
, metaPrepTime = Nothing
|
||||||
, metaCookTime = Nothing
|
, metaCookTime = Nothing
|
||||||
, metaDifficulty = Nothing
|
, metaDifficulty = Nothing
|
||||||
, metaCuisine = Nothing
|
, metaCuisine = Nothing
|
||||||
, metaDiet = []
|
, metaDiet = []
|
||||||
, metaTags = []
|
, metaTags = []
|
||||||
, metaImage = Nothing
|
, metaImage = Nothing
|
||||||
, metaDescription = Nothing
|
, metaDescription = Nothing
|
||||||
, metaCustom = Map.empty
|
, metaCustom = Map.empty
|
||||||
}
|
}
|
||||||
|
|||||||
+48
-44
@@ -12,62 +12,66 @@ spec =
|
|||||||
it "parses a single step of plain text" $ do
|
it "parses a single step of plain text" $ do
|
||||||
let input = "Hello, world."
|
let input = "Hello, world."
|
||||||
expected =
|
expected =
|
||||||
Right Recipe
|
Right
|
||||||
{ recipeMetadata = emptyMetadata
|
Recipe
|
||||||
, recipeSections =
|
{ recipeMetadata = emptyMetadata
|
||||||
Section
|
, recipeSections =
|
||||||
{ sectionName = Nothing
|
Section
|
||||||
, sectionSteps =
|
{ sectionName = Nothing
|
||||||
Step
|
, sectionSteps =
|
||||||
{ unStep = [StepText "Hello, world."]
|
Step
|
||||||
}
|
{ unStep = [StepText "Hello, world."]
|
||||||
:| []
|
}
|
||||||
}
|
:| []
|
||||||
:| []
|
}
|
||||||
}
|
:| []
|
||||||
|
}
|
||||||
parseCookFile input `shouldBe` expected
|
parseCookFile input `shouldBe` expected
|
||||||
|
|
||||||
it "parses multiple steps separated by a blank line" $ do
|
it "parses multiple steps separated by a blank line" $ do
|
||||||
let input = "Wash the potatoes.\n\nPeel and dice them."
|
let input = "Wash the potatoes.\n\nPeel and dice them."
|
||||||
expected =
|
expected =
|
||||||
Right Recipe
|
Right
|
||||||
{ recipeMetadata = emptyMetadata
|
Recipe
|
||||||
, recipeSections =
|
{ recipeMetadata = emptyMetadata
|
||||||
Section
|
, recipeSections =
|
||||||
{ sectionName = Nothing
|
Section
|
||||||
, sectionSteps =
|
{ sectionName = Nothing
|
||||||
Step {unStep = [StepText "Wash the potatoes."]}
|
, sectionSteps =
|
||||||
:| [ Step {unStep = [StepText "Peel and dice them."]}
|
Step{unStep = [StepText "Wash the potatoes."]}
|
||||||
]
|
:| [ Step{unStep = [StepText "Peel and dice them."]}
|
||||||
}
|
]
|
||||||
:| []
|
}
|
||||||
}
|
:| []
|
||||||
|
}
|
||||||
parseCookFile input `shouldBe` expected
|
parseCookFile input `shouldBe` expected
|
||||||
|
|
||||||
it "handles empty input by returning an empty recipe" $ do
|
it "handles empty input by returning an empty recipe" $ do
|
||||||
let expected =
|
let expected =
|
||||||
Right Recipe
|
Right
|
||||||
{ recipeMetadata = emptyMetadata
|
Recipe
|
||||||
, recipeSections =
|
{ recipeMetadata = emptyMetadata
|
||||||
Section
|
, recipeSections =
|
||||||
{ sectionName = Nothing
|
Section
|
||||||
, sectionSteps = Step {unStep = []} :| []
|
{ sectionName = Nothing
|
||||||
}
|
, sectionSteps = Step{unStep = []} :| []
|
||||||
:| []
|
}
|
||||||
}
|
:| []
|
||||||
|
}
|
||||||
parseCookFile "" `shouldBe` expected
|
parseCookFile "" `shouldBe` expected
|
||||||
|
|
||||||
it "trims leading and trailing whitespace from each step" $ do
|
it "trims leading and trailing whitespace from each step" $ do
|
||||||
let input = "\n\n Mix well. \n\n"
|
let input = "\n\n Mix well. \n\n"
|
||||||
expected =
|
expected =
|
||||||
Right Recipe
|
Right
|
||||||
{ recipeMetadata = emptyMetadata
|
Recipe
|
||||||
, recipeSections =
|
{ recipeMetadata = emptyMetadata
|
||||||
Section
|
, recipeSections =
|
||||||
{ sectionName = Nothing
|
Section
|
||||||
, sectionSteps =
|
{ sectionName = Nothing
|
||||||
Step {unStep = [StepText "Mix well."]} :| []
|
, sectionSteps =
|
||||||
}
|
Step{unStep = [StepText "Mix well."]} :| []
|
||||||
:| []
|
}
|
||||||
}
|
:| []
|
||||||
|
}
|
||||||
parseCookFile input `shouldBe` expected
|
parseCookFile input `shouldBe` expected
|
||||||
|
|||||||
Reference in New Issue
Block a user