herms 1.8.1.4 → 1.8.2.1
raw patch · 5 files changed
+153/−41 lines, 5 filesdep ~brickdep ~vty
Dependency ranges changed: brick, vty
Files
- README.md +43/−9
- app/herms.hs +57/−28
- data/config.hs +14/−0
- herms.cabal +5/−4
- src/ReadConfig.hs +34/−0
README.md view
@@ -17,6 +17,13 @@ - Generate shopping lists - Keep track of recipes with tags +### What's new:+- Set default unit systems, serving sizes, and recipe file path in ``config.hs``!+ - Coming soon: Multi-language support+- Added support for multi-word tags+- Check your Herm's version with ``-v`` or ``--version``+- Herm's is now on Stackage!+ ### Installation #### PATH setup@@ -29,15 +36,9 @@ #### Download and install -You have options!--Via Hackage and Cabal:-```-cabal update-cabal install herms-```+At the moment, Herm's can only be compiled from source, but binaries are in the works! -Manually cloning and installing from source with stack (recommended):+##### Manually cloning and installing from source with Stack _(recommended)_: ``` git clone https://github.com/JackKiefer/herms@@ -46,9 +47,18 @@ stack install ``` -You can also manually compile with cabal, but your milage may vary with dependency resolution:+##### Via Hackage and Cabal: ```+cabal update+cabal install herms+```++##### Manually with Cabal:++Your milage may vary with dependency resolution++``` git clone https://github.com/JackKiefer/herms cd herms cabal update@@ -56,6 +66,13 @@ ``` ### Usage++#### Command-line interface ++Herm's has a pretty intuitive interface for users familiar with other command-line programs! ++Below is the exhaustive list of all commands and their functionalities. Take a gander!+ ``` Usage: @@ -76,10 +93,14 @@ herms shopping RECIPE_NAMES [-s|--serving INT] generate shopping list for particular recipes + herms datadir print location of recipe and config files+ Available options: -h|--help Show this help text + -v|--version Show version+ RECIPE_NAME index or Recipe name --sort SORT_ORDER 'tags' to sort by tags@@ -99,6 +120,19 @@ -c|--convert CONV_UNIT view the recipe converted to imperial or metric E.g., 'herms view 2 -c imperial' ```++#### Configuring Herm's and managing recipe files++Herm's stores its recipes file, ``recipes.herms``, and configuration file, ``config.hs``, in the same directory. Herm's keeps track of its location! Run ``herms datadir`` to see the full directory path.++``config.hs`` is a pseudo-Haskell-source-code file with several options for configuring the behaviour of Herm's. It currently supports the following options:++- `defaultUnit` : The default unit system to show recipes in. Options: `Imperial`, `Metric`, `None`. Setting to `None` will simply show recipes in whatever unit system they're stored in.+- `defaultServingSize` : Default serving size to calculate when showing recipes. Can be any non-negative integer; set to `0` for no default. This can be useful when you're always cooking for the same number of people!+- `recipesFile` : The recipes file and relative location. Make sure the file already exists by running `touch FILENAME` before running Herm's or it will throw a fit. This option currently supports relative location, as well; if your data directory is `~/herms/data`, for example, and you want a recipe file in your home directoy called `~/GrandmasHugeCookbook.herms`, set this option to `"../../GrandmasHugeCookbook.herms"`.+++--- In honor of Logan, Utah's greatest Breakfast & Brunch.
app/herms.hs view
@@ -18,21 +18,29 @@ import RichText import Types import UnitConversions+import ReadConfig import Paths_herms -- Global constants-recipesFileName :: String-recipesFileName = "recipes.herms"- versionStr :: String-versionStr = "1.8.1.4"+versionStr = "1.8.2.0" -getRecipeBook :: IO [Recipe]-getRecipeBook = do- fileName <- getDataFileName recipesFileName+configPath :: IO FilePath+configPath = getDataFileName "config.hs"++-- | @getRecipeBookWith reads in recipe book with already read-in config+getRecipeBookWith :: Config -> IO [Recipe]+getRecipeBookWith config = do+ fileName <- getDataFileName (recipesFile config) contents <- readFile fileName return $ map read $ lines contents +-- | @getRecipeBook reads in config before reading in recipe book+getRecipeBook :: IO [Recipe]+getRecipeBook = do+ config <- getConfig+ getRecipeBookWith config+ getRecipe :: String -> [Recipe] -> Maybe Recipe getRecipe target = listToMaybe . filter ((target ==) . recipeName) @@ -46,10 +54,11 @@ response <- getLine if response == "y" || response == "Y" then do- recipeBook <- getRecipeBook+ config <- getConfig+ recipeBook <- getRecipeBookWith config let recpName = if isNothing oldRecp then recipeName newRecipe else recipeName (fromJust oldRecp) unless (isNothing (readRecipeRef recpName recipeBook)) $ removeSilent [recpName]- fileName <- getDataFileName recipesFileName+ fileName <- getDataFileName (recipesFile config) appendFile fileName (show newRecipe ++ "\n") putStrLn "Recipe saved!" else if response == "n" || response == "N"@@ -100,12 +109,13 @@ importFile :: String -> IO () importFile target = do- recipeBook <- getRecipeBook+ config <- getConfig+ recipeBook <- getRecipeBookWith config otherRecipeBook <- (map read . lines) <$> readFile target let recipeEq = (==) `on` recipeName let newRecipeBook = deleteFirstsBy recipeEq recipeBook otherRecipeBook ++ otherRecipeBook- replaceDataFile recipesFileName $ unlines $ show <$> newRecipeBook+ replaceDataFile (recipesFile config) $ unlines $ show <$> newRecipeBook if null otherRecipeBook then putStrLn "Nothing to import" else do@@ -115,14 +125,17 @@ view :: [String] -> Int -> String -> IO () view targets serv convName = do- recipeBook <- getRecipeBook+ config <- getConfig+ recipeBook <- getRecipeBookWith config let servings = case serv of- 0 -> Nothing+ 0 -> (case defaultServingSize config of+ 0 -> Nothing+ j -> Just j) i -> Just i let conv = case convName of "metric" -> Metric "imperial" -> Imperial- _ -> None + _ -> (defaultUnit config) forM_ targets $ \ target -> putText $ case readRecipeRef target recipeBook of Nothing -> target ~~ " does not exist\n"@@ -130,14 +143,17 @@ viewByStep :: [String] -> Int -> String -> IO () viewByStep targets serv convName = do- recipeBook <- getRecipeBook+ config <- getConfig+ recipeBook <- getRecipeBookWith config let servings = case serv of- 0 -> Nothing+ 0 -> (case defaultServingSize config of+ 0 -> Nothing+ j -> Just j) i -> Just i let conv = case convName of "metric" -> Metric "imperial" -> Imperial- _ -> None+ _ -> (defaultUnit config) hSetBuffering stdout NoBuffering forM_ targets $ \ target -> case readRecipeRef target recipeBook of Nothing -> putStr $ target ++ " does not exist\n"@@ -225,7 +241,8 @@ removeWithVerbosity :: Bool -> [String] -> IO () removeWithVerbosity v targets = do- recipeBook <- getRecipeBook+ config <- getConfig+ recipeBook <- getRecipeBookWith config mrecipes <- forM targets $ \ target -> do -- Resolve the recipes all at once; this way if we remove multiple -- recipes based on their respective index, all of the index are@@ -238,7 +255,7 @@ return mrecp -- Remove all the resolved recipes at once let newRecipeBook = recipeBook \\ catMaybes mrecipes- replaceDataFile recipesFileName $ unlines $ show <$> newRecipeBook+ replaceDataFile (recipesFile config) $ unlines $ show <$> newRecipeBook remove :: [String] -> IO () remove = removeWithVerbosity True@@ -262,10 +279,16 @@ forM_ (sort ingrts) $ \ingr -> putStrLn $ showIngredient 1 ingr +printDataDir :: IO ()+printDataDir = do+ dir <- getDataDir+ putStrLn $ filter (/= '\"') (show dir) ++ "/"+ -- Writes an empty recipes file if it doesn't exist checkFileExists :: IO () checkFileExists = do- fileName <- getDataFileName recipesFileName+ config <- getConfig+ fileName <- getDataFileName (recipesFile config) fileExists <- doesFileExist fileName unless fileExists (do dirName <- getDataDir@@ -285,6 +308,7 @@ runWithOpts (View targets serving step conversion) = if step then viewByStep targets serving conversion else view targets serving conversion runWithOpts (Shop targets serving) = shop targets serving+runWithOpts DataDir = printDataDir ------------------------------@@ -299,16 +323,18 @@ | Remove [String] -- ^ removes specified recipes | View [String] Int Bool String -- ^ shows specified recipes with given serving | Shop [String] Int -- ^ generates the shopping list for given recipes+ | DataDir -- ^ prints the directory of recipe file and config.hs -listP, addP, editP, removeP, viewP, shopP :: Parser Command-listP = List <$> (words <$> tagsP) <*> groupByTagsP <*> nameOnlyP-addP = pure Add-editP = Edit <$> recipeNameP-importP = Import <$> fileNameP-removeP = Remove <$> severalRecipesP-viewP = View <$> severalRecipesP <*> servingP <*> stepP <*> conversionP-shopP = Shop <$> severalRecipesP <*> servingP+listP, addP, editP, removeP, viewP, shopP, dataDirP :: Parser Command+listP = List <$> (words <$> tagsP) <*> groupByTagsP <*> nameOnlyP+addP = pure Add+editP = Edit <$> recipeNameP+importP = Import <$> fileNameP+removeP = Remove <$> severalRecipesP+viewP = View <$> severalRecipesP <*> servingP <*> stepP <*> conversionP+shopP = Shop <$> severalRecipesP <*> servingP+dataDirP = pure DataDir -- | @groupByTagsP is flag for grouping recipes by tags@@ -399,6 +425,9 @@ <> command "shopping" (info (helper <*> shopP) (progDesc "generate a shopping list for given recipes"))+ <> command "datadir"+ (info (helper <*> dataDirP)+ (progDesc "show location of recipe and config files")) versionOption :: Parser (a -> a) versionOption = infoOption versionStr
+ data/config.hs view
@@ -0,0 +1,14 @@+Config+{ +-- Default unit system to show recipes in.+-- OPTIONS: Imperial, Metric, None+ defaultUnit = None++-- Default serving size to calculate when showing recipes.+-- Set to 0 for no default.+, defaultServingSize = 0++-- Recipes file name and relative location.+-- Default is "recipes.herms"+, recipesFile = "recipes.herms"+}
herms.cabal view
@@ -16,7 +16,7 @@ -- PVP summary: +-+------- New features or improvements with significant API change -- | | +----- Bugfixes and improvements with minor change to API -- | | | +--- Bugfixes and improvements with no change to API-version: 1.8.1.4+version: 1.8.2.1 -- A short (one-line) description of the package. synopsis: A command-line manager for delicious kitchen recipes@@ -55,7 +55,7 @@ cabal-version: >=1.10 -- Files available to the program at runtime-data-files: recipes.herms+data-files: recipes.herms, config.hs -- The directory name for the aboce files data-dir: data@@ -69,6 +69,7 @@ other-modules: Utils , AddCLI , RichText+ , ReadConfig , Types , UnitConversions , Paths_herms@@ -79,14 +80,14 @@ -- Other library packages from which modules are imported. build-depends: ansi-terminal >= 0.7.0 && <= 0.8.1 , base >=4.8 && <5- , brick >=0.19 && <= 0.34.1+ , brick >= 0.35 && < 0.36 , directory >= 0.0 , microlens >=0.4 && <0.5 , microlens-th >=0.4 && <0.5 , optparse-applicative >=0.14 && <0.15 , semigroups >= 0.18.3 && <= 0.18.4 , split >=0.2 && <0.3- , vty >=5.15 && <=5.20+ , vty >=5.21 && < 5.22 -- Directories containing source files.
+ src/ReadConfig.hs view
@@ -0,0 +1,34 @@+module ReadConfig where++import UnitConversions+import qualified Text.Read as TR+import Control.Exception+import Data.Typeable+import Data.List.Split+import Paths_herms++data Config = Config+ { defaultUnit :: Conversion+ , defaultServingSize :: Int+ , recipesFile :: String+ } deriving (Read, Show)++data ConfigParseError = ConfigParseError + deriving Typeable++instance Show ConfigParseError where+ show ConfigParseError = "Error parsing config.hs"++instance Exception ConfigParseError++dropComments :: String -> String+dropComments = unlines . map (head . (splitOn "--")) . lines++getConfig :: IO Config+getConfig = do+ fileName <- getDataFileName "config.hs"+ contents <- readFile fileName+ let result = TR.readEither (dropComments contents) :: Either String Config+ case result of+ Left str -> throw ConfigParseError+ Right r -> return r