Skip to content

Commit abe0b6d

Browse files
committed
Initial draft
1 parent 9978caa commit abe0b6d

4 files changed

Lines changed: 167 additions & 0 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
module FamilyRecipes
2+
3+
type ParseError =
4+
| MissingTitle
5+
| MissingIngredients
6+
| MissingInstructions
7+
8+
type Recipe = {
9+
Title: string
10+
Ingredients: string
11+
Instructions: string
12+
}
13+
14+
type ParseState =
15+
| ReadingTitle
16+
| SeekingIngredientsHeading
17+
| ReadingIngredientsList
18+
| SeekingInstructionsHeading
19+
| ReadingInstructions
20+
21+
let rec parseLines (lines: string array, state: ParseState, recipe: Recipe): Result<Recipe, ParseError> =
22+
match state with
23+
| ReadingTitle -> parseTitle lines recipe
24+
| SeekingIngredientsHeading -> parseIngredientsHeading lines recipe
25+
| ReadingIngredientsList -> parseIngredientsList lines recipe
26+
| SeekingInstructionsHeading -> parseInstructionsHeading lines recipe
27+
| ReadingInstructions -> parseReadingInstructions lines recipe
28+
29+
and parseTitle lines recipe =
30+
if lines.Length = 0 || lines[0].Length = 0 then
31+
Error MissingTitle
32+
else
33+
parseLines(lines[1..], SeekingIngredientsHeading, {recipe with Title = lines[0]})
34+
35+
and parseIngredientsHeading lines recipe =
36+
if lines.Length = 0 then
37+
Error MissingIngredients
38+
else
39+
let nextState = if lines[0] = "Ingredients:" then ReadingIngredientsList else SeekingIngredientsHeading
40+
parseLines(lines[1..], nextState, recipe)
41+
42+
and parseIngredientsList lines recipe =
43+
if lines.Length = 0 then
44+
if recipe.Ingredients.Length = 0 then
45+
Error MissingIngredients
46+
else
47+
Error MissingInstructions
48+
elif lines[0].Length = 0 then
49+
if recipe.Ingredients.Length = 0 then
50+
Error MissingIngredients
51+
else
52+
parseLines(lines[1..], SeekingInstructionsHeading, recipe)
53+
else
54+
parseLines(lines[1..], ReadingIngredientsList, {recipe with Ingredients = lines[0]})
55+
56+
and parseInstructionsHeading lines recipe =
57+
if lines.Length = 0 then
58+
Error MissingInstructions
59+
else
60+
let nextState = if lines[0] = "Instructions:" then ReadingInstructions else SeekingInstructionsHeading
61+
parseLines(lines[1..], nextState, recipe)
62+
63+
and parseReadingInstructions lines recipe =
64+
if lines.Length = 0 then
65+
if recipe.Instructions.Length = 0 then
66+
Error MissingInstructions
67+
else
68+
Ok(recipe)
69+
else
70+
parseLines(lines[1..], ReadingInstructions, {recipe with Instructions = recipe.Instructions + lines[0]})
71+
72+
let parse (input: string) : Result<Recipe, ParseError> =
73+
parseLines(input.Split('\n'), ReadingTitle, {Title = ""; Ingredients = ""; Instructions = ""})
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
module FamilyRecipes
2+
3+
type ParseError =
4+
| MissingTitle
5+
| MissingIngredients
6+
| MissingInstructions
7+
8+
type Recipe = {
9+
Title: string
10+
Ingredients: string
11+
Instructions: string
12+
}
13+
14+
let parse input =
15+
failwith "Please implement this function"
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net9.0</TargetFramework>
5+
6+
<IsPackable>false</IsPackable>
7+
</PropertyGroup>
8+
9+
<ItemGroup>
10+
<Compile Include="FamilyRecipes.fs" />
11+
<Compile Include="FamilyRecipesTests.fs" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
16+
<PackageReference Include="xunit" Version="2.4.1" />
17+
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
18+
<PackageReference Include="FsUnit.xUnit" Version="4.0.4" />
19+
</ItemGroup>
20+
21+
</Project>
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
module Tests
2+
3+
open FsUnit.Xunit
4+
open Xunit
5+
6+
open FamilyRecipes
7+
8+
[<Fact>]
9+
let ``Error on blank recipe`` () =
10+
let expected: Result<Recipe, ParseError> = Error MissingTitle
11+
parse "" |> should equal expected
12+
13+
[<Fact>]
14+
let ``Error on title without ingredients or instructions`` () =
15+
let expected: Result<Recipe, ParseError> = Error MissingIngredients
16+
parse "foo" |> should equal expected
17+
18+
[<Fact>]
19+
let ``Error on title and ingredients heading without ingredients list`` () =
20+
let expected: Result<Recipe, ParseError> = Error MissingIngredients
21+
parse "A Title\n\nIngredients:" |> should equal expected
22+
23+
[<Fact>]
24+
let ``Error on title and ingredients without instructions`` () =
25+
let expected: Result<Recipe, ParseError> = Error MissingInstructions
26+
parse "A Title\n\nIngredients:\nsome ingredient" |> should equal expected
27+
28+
[<Fact>]
29+
let ``Error on all required sections but with empty ingredients list`` () =
30+
let expected: Result<Recipe, ParseError> = Error MissingIngredients
31+
parse "A Title\n\nIngredients:\n\nInstructions:\nSome instructions" |> should equal expected
32+
33+
[<Fact>]
34+
let ``Error on all required sections but missing instructions`` () =
35+
let expected: Result<Recipe, ParseError> = Error MissingInstructions
36+
parse "A Title\n\nIngredients:\nSome ingredient\n\nInstructions:" |> should equal expected
37+
38+
[<Fact>]
39+
let ``Success on minimal recipe`` () =
40+
let expected: Result<Recipe, ParseError> = Ok {
41+
Title = "Glass of Wine"
42+
Ingredients = "1 cup wine"
43+
Instructions = "Pour wine into wine glass."
44+
}
45+
let input = """Glass of Wine
46+
47+
Ingredients:
48+
1 cup wine
49+
50+
Instructions:
51+
Pour wine into wine glass.
52+
"""
53+
parse input |> should equal expected
54+
55+
// TODO: Make Ingredients a list of Ingredient records, with quantity, units, and substance
56+
// TODO: Add Ingredients tests for missing/bad quantities, missing/bad units, missing substance
57+
// TODO: Test the ability to parse fractional quantities
58+
// TODO: Organize into tasks

0 commit comments

Comments
 (0)