Skip to content

Commit

Permalink
Fix unit conversion (#465)
Browse files Browse the repository at this point in the history
  • Loading branch information
reaper47 authored Dec 28, 2024
1 parent e6a8de7 commit 83bbf90
Show file tree
Hide file tree
Showing 15 changed files with 121 additions and 101 deletions.
9 changes: 3 additions & 6 deletions internal/models/recipe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,7 @@ func TestRecipe_ConvertMeasurementSystem(t *testing.T) {
{
name: "imperial to metric",
in: models.Recipe{
Description: "Preheat the oven to 351 °F (177 °C). " +
"Stir in flour, chocolate chips, and walnuts. " +
"Drop spoonfuls of dough 1.18 inches apart onto ungreased baking sheets. " +
"Bake in the preheated oven until edges are nicely browned, about 10 minutes.",
Description: "Preheat the oven to 351 °F (177 °C). Stir in flour, chocolate chips, and walnuts. Drop spoonfuls of dough 3 cm apart onto ungreased baking sheets. Bake in the preheated oven until edges are nicely browned, about 10 minutes.",
Ingredients: []string{
"1 cup butter, softened",
"2 eggs",
Expand All @@ -166,7 +163,7 @@ func TestRecipe_ConvertMeasurementSystem(t *testing.T) {
},
to: units.MetricSystem,
want: models.Recipe{
Description: "Preheat the oven to 177 °C (177 °C). " +
Description: "Preheat the oven to 351 °F (177 °C). " +
"Stir in flour, chocolate chips, and walnuts. " +
"Drop spoonfuls of dough 3 cm apart onto ungreased baking sheets. " +
"Bake in the preheated oven until edges are nicely browned, about 10 minutes.",
Expand All @@ -181,7 +178,7 @@ func TestRecipe_ConvertMeasurementSystem(t *testing.T) {
"14.79 ml plus 5 ml adobo sauce (or to taste (from canned chipotle peppers))",
},
Instructions: []string{
"Preheat the oven to 177 °C (175 °C).",
"Preheat the oven to 350 degrees F (175 degrees C).",
"Stir in flour, chocolate chips, and walnuts.",
"Drop spoonfuls of dough 5.08 cm apart onto ungreased baking sheets.",
"Bake in the preheated oven until edges are nicely browned, about 10 minutes.",
Expand Down
3 changes: 2 additions & 1 deletion internal/models/schema-recipe.go
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ func (k *Keywords) UnmarshalJSON(data []byte) error {
}
k.Values = strings.TrimSpace(strings.Join(xs, ","))
case map[string]any:
fmt.Println("fuck")
fmt.Println("oops")
}

k.Values = strings.TrimRight(k.Values, ",")
Expand Down Expand Up @@ -604,6 +604,7 @@ func (i *Ingredients) UnmarshalJSON(data []byte) error {
{"œ", "œ"},
{"é", "é"},
{"ï", "ï"},
{"&", "&"},
}

for _, v := range xv {
Expand Down
2 changes: 1 addition & 1 deletion internal/scraper/scraper_A_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -986,7 +986,7 @@ func TestScraper_A(t *testing.T) {
Values: []string{
"3 ounces cream cheese", "3 ounces quality blue cheese, crumbled",
"1/2 cup half and half", "1/4 cup sour cream",
"1/2 teaspoon Simon & Garfunkel Seasoning or Poultry Seasoning",
"1/2 teaspoon Simon & Garfunkel Seasoning or Poultry Seasoning",
"1/2 cup melted salted butter", "2 cloves minced or pressed garlic",
"1/2 cup Frank's Original RedHot Sauce",
"24 whole chicken wings ((about 4 pounds (1.8 kg) for 24 whole wings))",
Expand Down
6 changes: 3 additions & 3 deletions internal/scraper/scraper_B_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -679,9 +679,9 @@ func TestScraper_B(t *testing.T) {
"1 tbsp oil (for frying)", "1 onion (chopped)", "2 garlic cloves (chopped)",
"1 tsp paprika powder", "1 tsp chili powder (or more if you like it spicier)",
"1 tsp cumin",
"1x14 oz can black beans (8.5oz (240g) rinsed & drained, or sub other beans)",
"1 cup corn (rinsed & drained)", "14 oz chopped tomatoes (or passata)",
"salt and pepper (to taste)", "4 bell peppers (halved & seeded)",
"1x14 oz can black beans (8.5oz (240g) rinsed & drained, or sub other beans)",
"1 cup corn (rinsed & drained)", "14 oz chopped tomatoes (or passata)",
"salt and pepper (to taste)", "4 bell peppers (halved & seeded)",
"1 cup vegan grated cheese", "1 recipe Vegan Aioli",
"green hot peppers (or jalapeños, sliced)",
"scallions (sliced)",
Expand Down
6 changes: 3 additions & 3 deletions internal/scraper/scraper_E_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,18 +350,18 @@ func TestScraper_E(t *testing.T) {
Ingredients: &models.Ingredients{
Values: []string{
"2 pounds potatoes (peeled)", "3 large eggplants", "Olive oil (to brush)",
"Sea salt & pepper (to sprinkle)",
"Sea salt & pepper (to sprinkle)",
"3 cups cooked lentils ((about 1 1/2 cups dried))", "2 cups passata",
"1 cup chopped tomatoes", "1 Tbsp olive oil", "1 large onion (chopped)",
"2 cloves of garlic (minced)", "2 bay leaves", "1-2 tsp dried thyme",
"1 tsp oregano", "1 tsp paprika", "1 tsp coconut sugar (or brown sugar)",
"1 pinch of cinnamon",
"Sea salt & pepper (to taste)",
"Sea salt & pepper (to taste)",
"2 Tbsp vegan butter",
"2 cups plant-based milk",
"3 1/2 Tbsp cornstarch",
"2 Tbsp nutritional yeast",
"Sea salt & pepper (to taste)",
"Sea salt & pepper (to taste)",
"1 pinch of nutmeg",
"Vegan cheese (to taste (optional))",
},
Expand Down
2 changes: 1 addition & 1 deletion internal/scraper/scraper_G_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -822,7 +822,7 @@ func TestScraper_G(t *testing.T) {
"1 lime",
"1 garlic clove",
"​25g cashew nuts",
"​10g coriander & mint",
"​10g coriander & mint",
"​80g spinach",
"​130g bulgur wheat",
"​160g blanched peas",
Expand Down
10 changes: 5 additions & 5 deletions internal/scraper/scraper_S_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ func TestScraper_S(t *testing.T) {
"1 large egg, at room temperature",
"1 teaspoon salt",
"14 Tablespoons (196g) unsalted butter, cold",
"2 and 1/2 cups (313g) all-purpose flour (spooned & leveled), plus more for generously flouring hands, surface, and dough",
"2/3 cup filling (see recipe notes for options & cheese filling)",
"2 and 1/2 cups (313g) all-purpose flour (spooned & leveled), plus more for generously flouring hands, surface, and dough",
"2/3 cup filling (see recipe notes for options & cheese filling)",
"1 large egg",
"2 Tablespoons (30ml) milk",
"1 cup (120g) confectioners’ sugar",
Expand Down Expand Up @@ -194,7 +194,7 @@ func TestScraper_S(t *testing.T) {
Image: &models.Image{Value: anUploadedImage.String()},
Ingredients: &models.Ingredients{
Values: []string{
"2 large boneless skinless chicken breasts", "Salt & pepper (to taste)",
"2 large boneless skinless chicken breasts", "Salt & pepper (to taste)",
"Flour (for dredging)", "1 tablespoon olive oil",
"2 tablespoons butter (divided)", "1 whole head garlic (cloves peeled)",
"1/2 cup chicken broth or stock", "1/2 teaspoon lemon juice",
Expand Down Expand Up @@ -971,7 +971,7 @@ func TestScraper_S(t *testing.T) {
"1 tablespoon Dijon mustard",
"4 tablespoons pickle juice",
"2 tablespoons water",
"Coarse salt & black pepper (to taste)",
"Coarse salt & black pepper (to taste)",
},
},
Instructions: &models.Instructions{
Expand Down Expand Up @@ -1952,7 +1952,7 @@ func TestScraper_S(t *testing.T) {
Values: []string{
"4 chicken breasts (roughly 2 lbs; boneless skinless chicken thighs may also be used * see note 1)",
"11.5 oz can of corn kernels (drained; 341 mL)",
"15 oz can of black beans (drained & rinsed; optional)",
"15 oz can of black beans (drained & rinsed; optional)",
"1 red onion (sliced into strips)",
"1/2 cup chicken stock",
"2 cloves garlic",
Expand Down
2 changes: 1 addition & 1 deletion internal/scraper/scraper_V_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ func TestScraper_V(t *testing.T) {
"200 to 250 grams Paneer ((Indian cottage cheese) - cubed or diced)",
"1 teaspoon dry fenugreek leaves ((kasuri methi) - optional)",
"½ to 1 teaspoon Garam Masala (or tandoori masala)",
"2 to 3 tablespoons light cream (or half & half or 1 to 2 tablespoons heavy cream - optional)",
"2 to 3 tablespoons light cream (or half & half or 1 to 2 tablespoons heavy cream - optional)",
"¼ to 1 teaspoon sugar (- optional, add as required depending on the sourness of the tomatoes)",
"salt (as required)",
"1 to 2 tablespoons coriander leaves (- chopped, (cilantro) - optional)",
Expand Down
7 changes: 2 additions & 5 deletions internal/server/handlers_settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ func TestHandlers_Settings_MeasurementSystems(t *testing.T) {
"4.73 dl semisweet chocolate chips",
},
Instructions: []string{
"Preheat the oven to 177 °C (175 °C).",
"Preheat the oven to 350 degrees F (175 degrees C).",
"Stir in flour, chocolate chips, and walnuts.",
"Drop spoonfuls of dough 5.08 cm apart onto ungreased baking sheets.",
"Bake in the preheated oven until edges are nicely browned, about 10 minutes.",
Expand Down Expand Up @@ -642,10 +642,7 @@ func TestHandlers_Settings_MeasurementSystems(t *testing.T) {
system: units.ImperialSystem,
want: models.Recipes{
{
Description: "Preheat the oven to 351 °F (351 °F). " +
"Stir in flour, chocolate chips, and walnuts. " +
"Drop spoonfuls of dough 1.18 inches apart onto ungreased baking sheets. " +
"Bake in the preheated oven until edges are nicely browned, about 10 minutes.",
Description: "Preheat the oven to 177 °C (351 °F). Stir in flour, chocolate chips, and walnuts. Drop spoonfuls of dough 1.18 inches apart onto ungreased baking sheets. Bake in the preheated oven until edges are nicely browned, about 10 minutes.",
Ingredients: []string{
"1 cup butter, softened",
"2 eggs",
Expand Down
7 changes: 6 additions & 1 deletion internal/services/sqlite_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@ func (s *SQLiteService) addRecipe(r models.Recipe, userID int64, settings models
}
}

if settings.MeasurementSystem == units.ImperialSystem {
for i, ingredient := range r.Ingredients {
r.Ingredients[i] = units.ReplaceDecimalFractions(ingredient)
}
}

tx, err := s.DB.BeginTx(ctx, &sql.TxOptions{})
if err != nil {
return 0, err
Expand Down Expand Up @@ -390,7 +396,6 @@ func (s *SQLiteService) addRecipeTx(ctx context.Context, tx *sql.Tx, r models.Re
r.Ingredients = slices.DeleteFunc(extensions.Unique(r.Ingredients), func(s string) bool { return s == "" })
for i, ingredient := range r.Ingredients {
var ingredientID int64
ingredient = units.ReplaceDecimalFractions(ingredient)
err = tx.QueryRowContext(ctx, statements.InsertIngredient, ingredient).Scan(&ingredientID)
if err != nil {
return 0, err
Expand Down
59 changes: 59 additions & 0 deletions internal/units/measurements.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/neurosnap/sentences"
"github.com/reaper47/recipya/internal/utils/extensions"
"github.com/reaper47/recipya/internal/utils/regex"
"log/slog"
"math"
"regexp"
"slices"
Expand Down Expand Up @@ -761,7 +762,12 @@ func ConvertSentence(input string, from, to System) (string, error) {
return input, errors.New("the measurement system is unchanged")
}

if regex.UnitMetric.MatchString(input) && regex.UnitImperial.MatchString(input) {
return input, nil
}

input = ReplaceVulgarFractions(input)
input = sumQuantitiesWithSeparator(input)

var (
irregular string
Expand Down Expand Up @@ -799,6 +805,59 @@ func ConvertSentence(input string, from, to System) (string, error) {
return converted, nil
}

func sumQuantitiesWithSeparator(input string) string {
excluded := []string{
"to", // English
"à", // French
"zu", // German
"para", // Portuguese
"до", // Ukrainian
"için", // Turkish
"kwa", // Swahili
"naar", // Dutch
"προς", // Greek
"do", // Polish
"till", // Swedish
}

matches := regex.QuantityWithSeparator.FindAllStringSubmatch(input, -1)
if len(matches) > 0 && len(matches[0]) > 3 && !slices.ContainsFunc(excluded, func(s string) bool {
return strings.Contains(matches[0][0], s)
}) {
inputAttr := slog.String("input", input)

left, err := strconv.ParseFloat(matches[0][1], 64)
if err != nil {
slog.Warn("Could not convert left quantity from string to integer", inputAttr, "integer", matches[0][1])
}

var right float64
parts := strings.Split(matches[0][2], "/")
if len(parts) > 1 {
a, err := strconv.ParseFloat(parts[0], 64)
if err != nil {
slog.Warn("Could not convert numerator from string to integer", inputAttr, "integer", parts[0])
}

b, err := strconv.ParseFloat(parts[1], 64)
if err != nil {
slog.Warn("Could not convert denominator from string to integer", inputAttr, "integer", parts[1])
}

right = a / b
} else {
right, err = strconv.ParseFloat(matches[0][2], 64)
if err != nil {
slog.Warn("Could not convert right quantity from string to integer", inputAttr, "integer", matches[0][2])
}
}

return regex.QuantityWithSeparator.ReplaceAllString(input, strconv.FormatFloat(left+right, 'f', -1, 64))
}

return input
}

func parseIrregularQuantity(input string, matches []string, re *regexp.Regexp, to System) (string, error) {
match := strings.Replace(matches[1], "-", " ", 1)
parts := strings.Split(match, " ")
Expand Down
14 changes: 14 additions & 0 deletions internal/units/measurements_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,13 @@ func TestConvertSentence(t *testing.T) {
to: units.MetricSystem,
want: "Rope with 5 ml of rice.",
},
{
name: "tsp to mL special",
in: "1 and 1/2 teaspoons cornstarch*",
from: units.ImperialSystem,
to: units.MetricSystem,
want: "7.5 ml cornstarch*",
},
{
name: "tsp to dL",
in: "Rope with 200tsp. of rice.",
Expand Down Expand Up @@ -548,6 +555,13 @@ func TestConvertSentence(t *testing.T) {
to: units.MetricSystem,
want: "Rope with 946.35 L of rice.",
},
{
name: "no conversion required",
in: "2 and 1/4 cups (280g) all-purpose flour (spooned & leveled)",
from: units.ImperialSystem,
to: units.MetricSystem,
want: "2 and 1/4 cups (280g) all-purpose flour (spooned & leveled)",
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions internal/utils/regex/regex.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ var DimensionPattern = regexp.MustCompile(`(\d+)\s*x\s*(\d+).`)
// Quantity detects quantities, i.e. 1ml, 1 ml, 1l and 1 l.
var Quantity = regexp.MustCompile(`(?i)\d+\s*((ml|l\b)(°[cf])?|°[cf])`)

// QuantityWithSeparator detects quantities separated by a word.
var QuantityWithSeparator = regexp.MustCompile(`(?i)(\d+)\s+[a-zA-Z]+\s+(\d+(/\d+)?)`)

// Regular expressions related to HTML.
var (
// URL matches an HTTP or HTTPS address.
Expand Down
18 changes: 18 additions & 0 deletions internal/utils/regex/regex_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,24 @@ func TestRegex_Quantity(t *testing.T) {
}
}

func TestRegex_QuantityWithSeparator(t *testing.T) {
valid := []string{
"1 and 1/2 teaspoons vanilla extract",
}
assertRegex(t, valid, regex.QuantityWithSeparator)

invalid := []string{
"1 teaspoon vanilla extract",
}
for _, s := range invalid {
t.Run("regex is invalid "+s, func(t *testing.T) {
if regex.QuantityWithSeparator.MatchString(s) {
t.Errorf("got true when want false for %q", s)
}
})
}
}

func TestRegex_Anchor(t *testing.T) {
t.Run("anchor is valid", func(t *testing.T) {
a := `<a slot="guide-links-primary" href="https://www.youtube.com/about/press/" style="display: none;">`
Expand Down
Loading

0 comments on commit 83bbf90

Please sign in to comment.