packages feed

herms-1.8.1.3: src/Types.hs

module Types where

import Data.List
import Data.List.Split
import Data.Ratio
import Data.Ord
import RichText

data Ingredient = Ingredient { quantity :: Ratio Int
                             , unit :: String
                             , ingredientName :: String
                             , attribute :: String
                             } deriving (Show, Read)

instance Eq Ingredient where
  ingr1 == ingr2 =  ingredientName ingr1 == ingredientName ingr2
                 && unit ingr1 == unit ingr2

instance Ord Ingredient where
  ingr1 `compare` ingr2 =
    case ingredientName ingr1 `compare` ingredientName ingr2 of
      EQ -> case unit ingr1 `compare` unit ingr2 of
              EQ -> case attribute ingr1 `compare` attribute ingr2 of
                      EQ -> quantity ingr1 `compare` quantity ingr2
                      x  -> x
              x  -> x
      x  -> x

data Recipe = Recipe { recipeName :: String
                     , description :: String
                     , servingSize :: Int
                     , ingredients :: [Ingredient]
                     , directions :: [String]
                     , tags :: [String]
                     } deriving (Eq, Show, Read)

showFrac :: Ratio Int -> String
showFrac x
  | numerator x == denominator x = show (numerator x)
  | denominator x == 1 = show (numerator x)
  | whole > 0 = show whole ++ " " ++  showFrac (x - fromIntegral whole)
  | otherwise = show (numerator x) ++ "/" ++ show (denominator x)
  where whole = floor $ fromIntegral (numerator x) / fromIntegral (denominator x)

readFrac :: String -> Ratio Int
readFrac x
  | ' ' `elem` x = let xs = splitOn " " x in (read' (head xs) % 1) + readFrac (last xs)
  | '/' `elem` x = let xs = splitOn "/" x in read' (head xs) % read' (last xs)
  | otherwise = (read x :: Int) % 1
  where read' = read :: String -> Int

showIngredient :: Int -> Ingredient -> String
showIngredient servings i = qty ++ u ++ ingredientName i ++ att
  where qty = if quantity i == 0
                then ""
                else (showFrac (quantity i * (servings % 1))) ++ " "
        u   = if null (unit i) then "" else unit i ++ " "
        att = if null (attribute i) then "" else ", " ++ attribute i

makeIngredients :: [[String]] -> [Ingredient]
makeIngredients [] = []
makeIngredients (i:is) = Ingredient { quantity = q
                                    , unit = i !! 1
                                    , ingredientName = i !! 2
                                    , attribute = i !! 3
                                    } : makeIngredients is
                                    where q = if null (head i) then 0 % 1 else readFrac (head i)


readIngredients :: [[String]] -> [Ingredient]
readIngredients is = makeIngredients $ transpose $ fillVoid is (length (maximumBy (comparing length) is))

adjustIngredients :: Ratio Int -> [Ingredient] -> [Ingredient]
adjustIngredients factor = map adjustIngredient
  where adjustIngredient ingredient =
          ingredient{ quantity = (quantity ingredient * factor) }

showRecipe :: Recipe -> Maybe Int -> RichText
showRecipe r maybeServings =  showRecipeHeader r maybeServings
                ~~ "\n" ~~ unlines (showRecipeSteps r)

showRecipeHeader :: Recipe -> Maybe Int -> RichText
showRecipeHeader r maybeServings = nameBox
                ~~ "\n" ~~ description r ~~ "\n"
                ~~ bold "\nServings: " ~~ show servings ~~ "\n"
                ~~ bold "\nIngredients:\n"
                ~~ unlines (map ((++) "* " . showIngredient servings) (ingredients r))
                where filler = replicate (length (recipeName r) + 2) '-'
                      servings = maybe (servingSize r) id maybeServings
                      nameBox = fontColor Magenta $ bold $ "+--" ~~ filler ~~ "+\n"
                                  ~~ "|  " ~~ recipeName r ~~ "  |\n"
                                  ~~ "+--" ~~ filler ~~ "+\n"

showRecipeSteps :: Recipe -> [String]
showRecipeSteps r =  zipWith (\i d -> "(" ++ show i ++ ") " ++ d) [1..] (directions r)

readRecipe :: [[String]] -> Recipe
readRecipe r = Recipe { recipeName = n, description = des, servingSize = s,
                        ingredients = i, directions = dir, tags = t }
  where n   = concat $ head r
        des = concat $ r !! 1
        s   = read $ concat $ r !! 2
        i   = adjustIngredients (1 % s) $ readIngredients [r !! 3, r !! 4, r !! 5, r !! 6]
        dir = r !! 7
        unparsedTags = concat (r !! 8)
        t   = if ',' `elem` unparsedTags then
                map (dropWhile (== ' ')) $ splitOn "," unparsedTags
              else
                words unparsedTags

fillVoidTo :: [String] -> Int -> [String]
fillVoidTo xs n = if l < n then xs ++ replicate (n - l) "" else xs
  where l = length xs

fillVoid :: [[String]] -> Int -> [[String]]
fillVoid [] _ = []
fillVoid (x:xs) n = fillVoidTo x n : fillVoid xs n