herms 1.9.0.3 → 1.9.0.4
raw patch · 13 files changed
+221/−198 lines, 13 filesdep +filepathdep ~brickdep ~vty
Dependencies added: filepath
Dependency ranges changed: brick, vty
Files
- README.md +12/−2
- app/herms.hs +51/−84
- data/config.hs +2/−2
- herms.cabal +7/−4
- src/AddCLI.hs +62/−63
- src/AddFile.hs +0/−7
- src/Lang/French.hs +4/−4
- src/Lang/Pirate.hs +1/−1
- src/Lang/Portuguese.hs +1/−1
- src/Lang/Strings.hs +10/−10
- src/ReadConfig.hs +69/−15
- src/RichText.hs +0/−1
- src/UnitConversions.hs +2/−4
README.md view
@@ -24,7 +24,9 @@ ### What's new: - Bonjour! Herm's now has language support for Français (French), English, and Pirate. Set your language preferences in ``config.hs``! - These are but the first languages that Herm's is now capable of supporting. We need your help to translate it into others! Currently in progress: Português (Portuguese), Español (Spanish)+- Herm's now conforms to the [XDG Base Directory Specification](http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html). In short, `config.hs` and `recipes.herms` are now stored in `~/.config/herms` and `~/.local/share/herms` respectively on most Linux systems. + ### Installation At the moment, Herm's can only be installed via [stack](https://docs.haskellstack.org/en/stable/README/) or [cabal](https://www.haskell.org/cabal/), but standalone binaries are in the works!@@ -95,7 +97,7 @@ herms shopping RECIPE_NAMES [-s|--serving INT] generate shopping list for particular recipes - herms datadir print location of recipe and config files+ herms datadir show locations of recipe and config files Available options: @@ -125,7 +127,15 @@ #### 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.+Herm's stores files in the following locations:++- The configuration file, `config.hs` in the XDG configuration directory,+ typically `~/.config/herms` on most Linux systems++- The recipes file, `recipes.herms` in the XDG data directory,+ typically `~/.local/share/herms` on most Linux systems++To see where these are stored on your system, run ``herms datadir``. ``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:
app/herms.hs view
@@ -1,9 +1,10 @@ {-# LANGUAGE ViewPatterns #-} + module Main where -import System.Directory import System.IO+import System.Directory import Control.Monad import Control.Monad.IO.Class import Control.Monad.Reader@@ -18,12 +19,10 @@ import Text.Read import Utils import AddCLI-import AddFile import RichText import Types import UnitConversions import ReadConfig-import Paths_herms import Control.Exception import GHC.IO.Exception import Foreign.C.Error@@ -31,19 +30,15 @@ -- Global constants versionStr :: String-versionStr = "1.9.0.3"--configPath :: IO FilePath-configPath = getDataFileName "config.hs"+versionStr = "1.9.0.4" type HermsReader = ReaderT (Config, RecipeBook) -- | @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+getRecipeBookWith config =+ fmap (map read . lines) $+ readFileOrDefault "recipes.herms" (recipesFile' config) getRecipe :: String -> [Recipe] -> Maybe Recipe getRecipe target = listToMaybe . filter ((target ==) . recipeName)@@ -57,44 +52,33 @@ let newRecipe = readRecipe input liftIO $ putTextLn $ showRecipe t newRecipe Nothing liftIO $ putStrLn (t Str.saveRecipeYesNoEdit)- response <- liftIO $ getLine- if response == (t Str.y) || response == (t Str.yCap)+ response <- liftIO getLine+ if response == t Str.y || response == t Str.yCap then do let recpName = maybe (recipeName newRecipe) recipeName oldRecp unless (isNothing (readRecipeRef recpName recipeBook)) $ removeSilent [recpName]- fileName <- liftIO $ getDataFileName (recipesFile' config)- liftIO $ appendFile fileName (show newRecipe ++ "\n")+ liftIO $ appendFile (recipesFile' config) (show newRecipe ++ "\n") liftIO $ putStrLn (t Str.recipeSaved)- else if response == (t Str.n) || response == (t Str.nCap)+ else if response == t Str.n || response == t Str.nCap then liftIO $ putStrLn (t Str.changesDiscarded)- else if response == (t Str.e) || response == (t Str.eCap)+ else if response == t Str.e || response == t Str.eCap then doEdit newRecipe oldRecp else do- liftIO $ putStrLn ("\n" ++ (t Str.badEntry) ++ "\n")+ liftIO $ putStrLn ("\n" ++ t Str.badEntry ++ "\n") saveOrDiscard input oldRecp -addInteractive :: HermsReader IO ()-addInteractive = do- (config, recipeBook) <- ask+add :: HermsReader IO ()+add = do+ (config, _) <- ask input <- liftIO $ getAddInput (translator config) saveOrDiscard input Nothing --add :: [String] -> HermsReader IO ()-add [] = addInteractive-add filenames = do- (config, recipeBook) <- ask- forM_ filenames $ \ filename -> do- contents <- liftIO $ readFile filename- saveOrDiscard (readRecipeFileData contents) Nothing- - doEdit :: Recipe -> Maybe Recipe -> HermsReader IO () doEdit recp origRecp = do- (config, recipeBook) <- ask+ (config, _) <- ask input <- liftIO $ getEdit (translator config) (recipeName recp) (description recp) serving amounts units ingrs attrs dirs tag saveOrDiscard input origRecp where serving = show $ servingSize recp@@ -112,7 +96,7 @@ (config, recipeBook) <- ask let t = translator config case readRecipeRef target recipeBook of- Nothing -> liftIO $ putStrLn $ target ++ (t Str.doesNotExist)+ Nothing -> liftIO $ putStrLn $ target ++ t Str.doesNotExist Just recp -> doEdit recp (Just recp) -- Only supports editing one recipe per command @@ -146,7 +130,7 @@ let t = translator config liftIO $ forM_ targets $ \ target -> putText $ case readRecipeRef target recipeBook of- Nothing -> target ~~ (t Str.doesNotExist)+ Nothing -> target ~~ t Str.doesNotExist Just recp -> fromString $ show recp getServingsAndConv :: Int -> String -> Config -> (Maybe Int, Conversion)@@ -158,18 +142,18 @@ i -> Just i t = translator config conv- | convName == (t Str.metric) = Metric- | convName == (t Str.imperial) = Imperial+ | convName == t Str.metric = Metric+ | convName == t Str.imperial = Imperial | otherwise = defaultUnit' config view :: [String] -> Int -> String -> HermsReader IO () view targets serv convName = do- (config,recipeBook) <- ask+ (config, recipeBook) <- ask let t = translator config let (servings, conv) = getServingsAndConv serv convName config liftIO $ forM_ targets $ \ target -> putText $ case readRecipeRef target recipeBook of- Nothing -> target ~~ (t Str.doesNotExist)+ Nothing -> target ~~ t Str.doesNotExist Just recp -> showRecipe t (convertRecipeUnits conv recp) servings viewByStep :: [String] -> Int -> String -> HermsReader IO ()@@ -179,23 +163,23 @@ let (servings, conv) = getServingsAndConv serv convName config liftIO $ hSetBuffering stdout NoBuffering forM_ targets $ \ target -> case readRecipeRef target recipeBook of- Nothing -> liftIO $ putStr $ target ++ (t Str.doesNotExist)+ Nothing -> liftIO $ putStr $ target ++ t Str.doesNotExist Just recp -> viewRecipeByStep (convertRecipeUnits conv recp) servings viewRecipeByStep :: Recipe -> Maybe Int -> HermsReader IO () viewRecipeByStep recp servings = do- (config, recipeBook) <- ask+ (config, _) <- ask let t = translator config liftIO $ putText $ showRecipeHeader t recp servings let steps = showRecipeSteps recp liftIO $ forM_ (init steps) $ \ step -> do- putStr $ step ++ (t Str.more)+ putStr $ step ++ t Str.more getLine liftIO $ putStr $ last steps ++ "\n" list :: [String] -> Bool -> Bool -> HermsReader IO () list inputTags groupByTags nameOnly = do- (config,recipes) <- ask+ (_, recipes) <- ask let recipesWithIndex = zip [1..] recipes let targetRecipes = filterByTags inputTags recipesWithIndex if groupByTags@@ -210,7 +194,7 @@ listDefault :: Bool -> [(Int, Recipe)] -> HermsReader IO () listDefault nameOnly (unzip -> (indices, recipes)) = do- (config, recipeBook) <- ask+ (config, _) <- ask let recipeList = map (showRecipeInfo (translator config)) recipes size = length $ show $ length recipeList strIndices = map (padLeft size . show) indices@@ -220,7 +204,7 @@ listByTags :: Bool -> [String] -> [(Int, Recipe)] -> HermsReader IO () listByTags nameOnly inputTags recipesWithIdx = do- (config, recipeBook) <- ask+ (config, _) <- ask let tagsRecipes :: [[(String, (Int, Recipe))]] tagsRecipes = groupBy ((==) `on` fst) $ sortBy (compare `on` fst) $@@ -239,7 +223,7 @@ where name = fontColor Blue $ recipeName recipe desc = (takeFullWords . description) recipe showTags = fontColor Green $ (intercalate ", " . tags) recipe- tagsStr = fontColor White $ (t Str.capTags)+ tagsStr = fontColor White (t Str.capTags) takeFullWords :: String -> String takeFullWords = unwords . takeFullWords' 0 . words@@ -256,11 +240,10 @@ -- and finally moves the temporary file over to the target @fp@. replaceDataFile :: FilePath -> String -> IO ()-replaceDataFile fp str = do+replaceDataFile fileName str = do (tempName, tempHandle) <- openTempFile "." "herms_temp" hPutStr tempHandle str hClose tempHandle- fileName <- getDataFileName fp removeFile fileName let exdev e = if ioe_errno e == Just ((\(Errno a) -> a) eXDEV) then copyFile tempName fileName >> removeFile tempName@@ -282,8 +265,8 @@ -- remove anything let mrecp = readRecipeRef target recipeBook () <- putStr $ case mrecp of- Nothing -> target ++ (t Str.doesNotExist)- Just r -> guard v *> (t Str.removingRecipe) ++ recipeName r ++ "...\n"+ Nothing -> target ++ t Str.doesNotExist+ Just r -> guard v *> t Str.removingRecipe ++ recipeName r ++ "...\n" return mrecp -- Remove all the resolved recipes at once let newRecipeBook = recipeBook \\ catMaybes mrecipes@@ -297,7 +280,7 @@ shop :: [String] -> Int -> HermsReader IO () shop targets serv = do- (config, recipeBook) <- ask+ (_, recipeBook) <- ask let getFactor recp | serv == 0 = servingSize recp % 1 | otherwise = serv % 1@@ -312,19 +295,9 @@ printDataDir :: HermsReader IO () printDataDir = do- dir <- liftIO $ getDataDir- liftIO $ putStrLn $ filter (/= '\"') (show dir) ++ "/"---- Writes an empty recipes file if it doesn't exist-checkFileExists :: IO ()-checkFileExists = do- config <- getConfig- fileName <- getDataFileName (recipesFile' config)- fileExists <- doesFileExist fileName- unless fileExists (do- dirName <- getDataDir- createDirectoryIfMissing True dirName- writeFile fileName "")+ (config, _) <- ask+ liftIO $ mapM_ (putStrLn . cleanDirPath) [dataDir config, configDir config]+ where cleanDirPath = (++ "/") . filter (/= '\"') main :: IO () main = do@@ -336,14 +309,14 @@ -- @runWithOpts runs the action of selected command. runWithOpts :: Command -> HermsReader IO () runWithOpts (List tags group nameOnly) = list tags group nameOnly-runWithOpts (Add targets) = add targets+runWithOpts Add = add runWithOpts (Edit target) = edit target runWithOpts (Import target) = importFile target runWithOpts (Export targets) = export targets runWithOpts (Remove targets) = remove targets-runWithOpts (View targets serving step conversion) = if step +runWithOpts (View targets serving step conversion) = if step then viewByStep targets serving conversion- else view targets serving conversion + else view targets serving conversion runWithOpts (Shop targets serving) = shop targets serving runWithOpts DataDir = printDataDir @@ -355,26 +328,25 @@ -- | 'Command' data type represents commands of CLI data Command = List [String] Bool Bool -- ^ shows recipes | View [String] Int Bool String -- ^ shows specified recipes with given serving- | Add [String] -- ^ adds the recipe (interactively or from files)+ | Add -- ^ adds the recipe (interactively) | Edit String -- ^ edits the recipe | Import String -- ^ imports a recipe file | Export [String] -- ^ exports recipes to stdout | Remove [String] -- ^ removes specified recipes | Shop [String] Int -- ^ generates the shopping list for given recipes- | DataDir -- ^ prints the directory of recipe file and config.hs+ | DataDir -- ^ prints the directories of recipe file and config.hs -listP, addP, viewP, editP, importP, exportP, shopP, dataDirP :: Translator -> Parser Command-listP t = List <$> (words <$> (tagsP t)) <*> (groupByTagsP t) <*> (nameOnlyP t)-addP t = Add <$> (severalFilesP t)-editP t = Edit <$> (recipeNameP t)-importP t = Import <$> (fileNameP t)-exportP t = Export <$> (severalRecipesP t)-removeP t = Remove <$> (severalRecipesP t)-viewP t = View <$> (severalRecipesP t) <*> (servingP t) <*> (stepP t) <*> (conversionP t)-shopP t = Shop <$> (severalRecipesP t) <*> (servingP t)+listP, addP, viewP, editP, importP, exportP, removeP, shopP, dataDirP :: Translator -> Parser Command+listP t = List <$> (words <$> tagsP t) <*> groupByTagsP t <*> nameOnlyP t+addP _ = pure Add+editP t = Edit <$> recipeNameP t+importP t = Import <$> fileNameP t+exportP t = Export <$> severalRecipesP t+removeP t = Remove <$> severalRecipesP t+viewP t = View <$> severalRecipesP t <*> servingP t <*> stepP t <*> conversionP t+shopP t = Shop <$> severalRecipesP t <*> servingP t dataDirP _ = pure DataDir - -- | @groupByTagsP is flag for grouping recipes by tags groupByTagsP :: Translator -> Parser Bool groupByTagsP t = switch@@ -425,11 +397,6 @@ fileNameP t = strArgument ( metavar (t Str.fileNameMetavar) <> help (t Str.fileNameDesc)) --- | @severalFilesP parses several file names at once--- and returns th parser of a list of names-severalFilesP :: Translator -> Parser [String]-severalFilesP t = many (fileNameP t)- -- | @severalRecipesP parses several recipe names at once -- and returns the parser of list of names severalRecipesP :: Translator -> Parser [String]@@ -483,6 +450,6 @@ -- @prsr is the main parser of all CLI arguments. commandPI :: Translator -> ParserInfo Command-commandPI t = info ( helper <*> versionOption <*> (optP t))+commandPI t = info ( helper <*> versionOption <*> optP t) $ fullDesc <> progDesc (t Str.progDesc)
data/config.hs view
@@ -8,8 +8,8 @@ -- Set to 0 for no default. , defaultServingSize = 0 --- Recipes file name and relative location.--- Default is "recipes.herms"+-- Recipes file name relative to the user's XDG data dir, generally+-- ~/.local/share/herms on Linux systems. Default is "recipes.herms". , recipesFile = "recipes.herms" , language = "english" }
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.9.0.3+version: 1.9.0.4 -- A short (one-line) description of the package. synopsis: A command-line manager for delicious kitchen recipes@@ -65,10 +65,12 @@ -- .hs or .lhs file containing the Main module. main-is: herms.hs + Ghc-Options: -W -Wcompat+ -Wunrecognised-warning-flags+ -- Modules included in this executable, other than Main. other-modules: Utils , AddCLI- , AddFile , RichText , ReadConfig , Types@@ -85,15 +87,16 @@ -- 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.38+ , brick >= 0.19 && <= 0.39 , directory >= 0.0+ , filepath >= 1.4 && < 1.5 , microlens >=0.4 && <0.5 , microlens-th >=0.4 && <0.5 , mtl >= 2.2.1 && < 2.3 , optparse-applicative >=0.14 && <0.15 , semigroups >= 0.18.3 && < 0.19 , split >=0.2 && <0.3- , vty >=5.15 && <= 5.22+ , vty >=5.15 && <= 5.23 -- Directories containing source files.
src/AddCLI.hs view
@@ -3,7 +3,6 @@ {-# LANGUAGE RankNTypes #-} module AddCLI where -import Control.Monad import Lens.Micro import Lens.Micro.TH import qualified Graphics.Vty as V@@ -73,23 +72,23 @@ ui = C.center $ str (t Str.tuiTitle) <=> str " " <=>- (str (t Str.tuiName) <+> (hLimit 62 e1)) <=>+ (str (t Str.tuiName) <+> hLimit 62 e1) <=> str " " <=>- (str (t Str.tuiDesc) <+> (hLimit 62 $ vLimit 4 e2)) <=>+ (str (t Str.tuiDesc) <+> hLimit 62 (vLimit 4 e2)) <=> str " " <=>- (str (t Str.tuiServingSize) <+> (hLimit 3 e3)) <=>+ (str (t Str.tuiServingSize) <+> hLimit 3 e3) <=> str " " <=> str (t Str.tuiHeaders) <=> (str (t Str.tuiIngrs)- <+> (hLimit 7 $ vLimit 7 e4)- <+> (hLimit 9 $ vLimit 7 e5)- <+> (hLimit 28 $ vLimit 7 e6)- <+> (hLimit 18 $ vLimit 7 e7))+ <+> hLimit 7 (vLimit 7 e4)+ <+> hLimit 9 (vLimit 7 e5)+ <+> hLimit 28 (vLimit 7 e6)+ <+> hLimit 18 (vLimit 7 e7)) <=> str " " <=>- (str (t Str.tuiDirs) <+> (hLimit 62 $ vLimit 8 e8)) <=>+ (str (t Str.tuiDirs) <+> hLimit 62 (vLimit 8 e8)) <=> str " " <=>- (str (t Str.tuiTags) <+> (hLimit 62 $ vLimit 1 e9)) <=>+ (str (t Str.tuiTags) <+> hLimit 62 (vLimit 1 e9)) <=> str " " <=> str (t Str.tuiHelp1) <=> str (t Str.tuiHelp2) <=>@@ -105,23 +104,23 @@ -- Ctrl + <Arrow Keys> V.EvKey V.KDown [V.MCtrl] ->- M.continue $ st & focusRing %~ (determineNextFocus FocusDown st)+ M.continue $ st & focusRing %~ determineNextFocus FocusDown st V.EvKey V.KUp [V.MCtrl] ->- M.continue $ st & focusRing %~ (determineNextFocus FocusUp st)+ M.continue $ st & focusRing %~ determineNextFocus FocusUp st V.EvKey V.KRight [V.MCtrl] ->- M.continue $ st & focusRing %~ (determineNextFocus FocusRight st)+ M.continue $ st & focusRing %~ determineNextFocus FocusRight st V.EvKey V.KLeft [V.MCtrl] ->- M.continue $ st & focusRing %~ (determineNextFocus FocusLeft st)+ M.continue $ st & focusRing %~ determineNextFocus FocusLeft st -- Meta + <h-j-k-l> V.EvKey (V.KChar 'h') [V.MMeta] ->- M.continue $ st & focusRing %~ (determineNextFocus FocusLeft st)+ M.continue $ st & focusRing %~ determineNextFocus FocusLeft st V.EvKey (V.KChar 'j') [V.MMeta] ->- M.continue $ st & focusRing %~ (determineNextFocus FocusDown st)+ M.continue $ st & focusRing %~ determineNextFocus FocusDown st V.EvKey (V.KChar 'k') [V.MMeta] ->- M.continue $ st & focusRing %~ (determineNextFocus FocusUp st)+ M.continue $ st & focusRing %~ determineNextFocus FocusUp st V.EvKey (V.KChar 'l') [V.MMeta] ->- M.continue $ st & focusRing %~ (determineNextFocus FocusRight st)+ M.continue $ st & focusRing %~ determineNextFocus FocusRight st _ -> M.continue =<< case F.focusGetCurrent (st^.focusRing) of Just RecipeName -> T.handleEventLensed st edit1 E.handleEditorEvent ev@@ -140,49 +139,49 @@ determineNextFocus action st = case action of FocusDown -> case currentFocus of- Just RecipeName -> (F.focusNext)- Just Description -> (F.focusNext)- Just ServingSize -> (F.focusNext)- Just IngrAmount -> (F.focusNext . F.focusNext . F.focusNext . F.focusNext)- Just IngrUnit -> (F.focusNext . F.focusNext . F.focusNext)- Just IngrName -> (F.focusNext . F.focusNext)- Just IngrAttr -> (F.focusNext)- Just Directions -> (F.focusNext)- Just Tags -> (F.focusNext)- Nothing -> (F.focusNext)+ Just RecipeName -> F.focusNext+ Just Description -> F.focusNext+ Just ServingSize -> F.focusNext+ Just IngrAmount -> F.focusNext . F.focusNext . F.focusNext . F.focusNext+ Just IngrUnit -> F.focusNext . F.focusNext . F.focusNext+ Just IngrName -> F.focusNext . F.focusNext+ Just IngrAttr -> F.focusNext+ Just Directions -> F.focusNext+ Just Tags -> F.focusNext+ Nothing -> F.focusNext FocusUp -> case currentFocus of- Just RecipeName -> (F.focusPrev)- Just Description -> (F.focusPrev)- Just ServingSize -> (F.focusPrev)- Just IngrAttr -> (F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev)- Just IngrName -> (F.focusPrev . F.focusPrev . F.focusPrev)- Just IngrUnit -> (F.focusPrev . F.focusPrev)- Just IngrAmount -> (F.focusPrev)- Just Directions -> (F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev)- Just Tags -> (F.focusPrev)- Nothing -> (F.focusPrev)+ Just RecipeName -> F.focusPrev+ Just Description -> F.focusPrev+ Just ServingSize -> F.focusPrev+ Just IngrAttr -> F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev+ Just IngrName -> F.focusPrev . F.focusPrev . F.focusPrev+ Just IngrUnit -> F.focusPrev . F.focusPrev+ Just IngrAmount -> F.focusPrev+ Just Directions -> F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev+ Just Tags -> F.focusPrev+ Nothing -> F.focusPrev FocusLeft -> case currentFocus of- Just RecipeName -> (F.focusNext . F.focusNext)- Just Description -> (F.focusNext)- Just ServingSize -> (F.focusNext)- Just IngrAmount -> (F.focusNext . F.focusNext . F.focusNext)- Just IngrUnit -> (F.focusPrev)- Just IngrName -> (F.focusPrev)- Just IngrAttr -> (F.focusPrev)- Just Directions -> (F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev)- Just Tags -> (F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev)- Nothing -> (F.focusNext)+ Just RecipeName -> F.focusNext . F.focusNext+ Just Description -> F.focusNext+ Just ServingSize -> F.focusNext+ Just IngrAmount -> F.focusNext . F.focusNext . F.focusNext+ Just IngrUnit -> F.focusPrev+ Just IngrName -> F.focusPrev+ Just IngrAttr -> F.focusPrev+ Just Directions -> F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev+ Just Tags -> F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev+ Nothing -> F.focusNext FocusRight -> case currentFocus of- Just RecipeName -> (F.focusNext . F.focusNext)- Just Description -> (F.focusNext)- Just ServingSize -> (F.focusNext)- Just IngrAmount -> (F.focusNext)- Just IngrUnit -> (F.focusNext)- Just IngrName -> (F.focusNext)- Just IngrAttr -> (F.focusPrev . F.focusPrev . F.focusPrev)- Just Directions -> (F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev)- Just Tags -> (F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev)- Nothing -> (F.focusNext)+ Just RecipeName -> F.focusNext . F.focusNext+ Just Description -> F.focusNext+ Just ServingSize -> F.focusNext+ Just IngrAmount -> F.focusNext+ Just IngrUnit -> F.focusNext+ Just IngrName -> F.focusNext+ Just IngrAttr -> F.focusPrev . F.focusPrev . F.focusPrev+ Just Directions -> F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev+ Just Tags -> F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev . F.focusPrev+ Nothing -> F.focusNext where currentFocus = F.focusGetCurrent $ st^.focusRing @@ -217,10 +216,10 @@ , M.appAttrMap = const theMap } -getEdit :: Translator -> String -> String -> String -> String -> String -> String -> String -> String -> String -> IO ([[String]])+getEdit :: Translator -> String -> String -> String -> String -> String -> String -> String -> String -> String -> IO [[String]] getEdit t name desc serving amounts units ingrs attrs dirs tags = do st <- M.defaultMain (theApp t) (initialState name desc serving amounts units ingrs attrs dirs tags)- return $ [ E.getEditContents $ st^.edit1+ return [ E.getEditContents $ st^.edit1 , E.getEditContents $ st^.edit2 , E.getEditContents $ st^.edit3 , E.getEditContents $ st^.edit4@@ -231,10 +230,10 @@ , E.getEditContents $ st^.edit9 ] -getAddInput :: Translator -> IO ([[String]])-getAddInput t = do +getAddInput :: Translator -> IO [[String]]+getAddInput t = do st <- M.defaultMain (theApp t) (initialState "" "" "" "" "" "" "" "" "")- return $ [ E.getEditContents $ st^.edit1+ return [ E.getEditContents $ st^.edit1 , E.getEditContents $ st^.edit2 , E.getEditContents $ st^.edit3 , E.getEditContents $ st^.edit4
− src/AddFile.hs
@@ -1,7 +0,0 @@-module AddFile where--import UnitConversions--readRecipeFileData :: String -> [[String]]-readRecipeFileData _ = [["Nazzzme"],["Des lol","djdj",""],["2"],["2","1 2/2"],["units","units"],["name","name2"],["lol","ehe"],["jejej","ejeje","kks","lslsl","slsl"],["spicy licy lol"]]-
src/Lang/French.hs view
@@ -9,7 +9,7 @@ | is tuiDesc = " Description: " | is tuiServingSize = " Portion: " | is tuiHeaders = " qté. unité nom attribut"- | is tuiIngrs = " Ingrédients: \n(un par ligne) " + | is tuiIngrs = " Ingrédients: \n(un par ligne) " | is tuiDirs = " Directions: \n(un par ligne) " | is tuiTags = " Mots-clés: " | is tuiHelp1 = " Tab / Shift+Tab - Champ suivant / précédent"@@ -19,11 +19,11 @@ | is headerServs = "\nPortions: " | is headerIngrs = "\nIngrédients:\n" | is saveRecipeYesNoEdit = "Enregister la recette? (O)ui (N)on (E)diter"- | is y = "o" + | is y = "o" | is yCap = "O"- | is n = "n" + | is n = "n" | is nCap = "N"- | is e = "e" + | is e = "e" | is eCap = "E" | is recipeSaved = "Recette enregistrée!" | is changesDiscarded = "Modifications annulées."
src/Lang/Pirate.hs view
@@ -8,7 +8,7 @@ | is tuiName = " Giv'r a name: " | is tuiDesc = " Describin': " | is tuiServingSize = " Servin' size: "- | is tuiIngrs = " Reagents: \n(one per line) " + | is tuiIngrs = " Reagents: \n(one per line) " | is tuiHelp1 = " Tab / Shift+Tab - Next / Previous bay" | is tuiHelp2 = " Ctrl + <Arrow keys> - Navigate the sea" | is tuiHelp3 = " [Meta or Alt] + <h-j-k-l> - Navigate the sea"
src/Lang/Portuguese.hs view
@@ -8,7 +8,7 @@ | is tuiName = " Giv'r a name: " | is tuiDesc = " Describin': " | is tuiServingSize = " Servin' size: "- | is tuiIngrs = " Reagents: \n(one per line) " + | is tuiIngrs = " Reagents: \n(one per line) " | is tuiHelp1 = " Tab / Shift+Tab - Next / Previous bay" | is tuiHelp2 = " Ctrl + <Arrow keys> - Navigate the sea" | is tuiHelp3 = " [Meta or Alt] + <h-j-k-l> - Navigate the sea"
src/Lang/Strings.hs view
@@ -3,15 +3,15 @@ -------------------------- ---- Add Recipe TUI ------ --------------------------- + -- *** Note: Whitespace is important!!! ***- + tuiTitle = " Herm's - Add a recipe" tuiName = " Name: " tuiDesc = " Description: " tuiServingSize = " Serving size: " tuiHeaders = " qty. unit name attribute"-tuiIngrs = " Ingredients: \n(one per line) " +tuiIngrs = " Ingredients: \n(one per line) " tuiDirs = " Directions: \n(one per line) " tuiTags = " Tags: " tuiHelp1 = " Tab / Shift+Tab - Next / Previous field"@@ -32,13 +32,13 @@ saveRecipeYesNoEdit = "Save recipe? (Y)es (N)o (E)dit" -y = "y" +y = "y" yCap = "Y" -n = "n" +n = "n" nCap = "N" -e = "e" +e = "e" eCap = "E" recipeSaved = "Recipe saved!"@@ -66,7 +66,7 @@ -------------------------- -- Options and Flags -- --------------------------- + group = "group" groupShort = 'g' groupDesc = "group recipes by tags"@@ -104,7 +104,7 @@ -------------------------- -- Commands -- --------------------------- + list = "list" listDesc = "list recipes" @@ -130,7 +130,7 @@ shoppingDesc = "generate a shopping list for given recipes" datadir = "datadir"-datadirDesc = "show location of recipe and config files"+datadirDesc = "show locations of recipe and config files" version = "version" versionShort = 'v'@@ -139,5 +139,5 @@ -------------------------- -- Misc -- --------------------------- + progDesc = "HeRM's: a Haskell-based Recipe Manager. Type \"herms --help\" for options"
src/ReadConfig.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE LambdaCase #-}+ module ReadConfig where import UnitConversions@@ -8,12 +10,14 @@ import Data.List.Split import Lang.Pirate import Lang.French-import Paths_herms+import System.FilePath ((</>))+import System.Directory+import Paths_herms hiding (getDataDir) -- This module is generated by cabal ------------------------------ ------- Config Types --------- ------------------------------- + -- TODO Allow record synonyms with that fancy stuff data ConfigInfo = ConfigInfo@@ -26,9 +30,11 @@ data Config = Config { defaultUnit' :: Conversion , defaultServingSize' :: Int+ , dataDir :: String+ , configDir :: String , recipesFile' :: String , translator :: String -> String- } + } data Language = English | Pirate@@ -41,7 +47,7 @@ ---- Exception Handling ------ ------------------------------ -data ConfigParseError = ConfigParseError +data ConfigParseError = ConfigParseError deriving Typeable instance Show ConfigParseError where@@ -87,35 +93,83 @@ getLang :: ConfigInfo -> Language getLang c- | isIn englishSyns = English -- | isIn portugueseSyns = Portuguese | isIn pirateSyns = Pirate -- | isIn frenchSyns = French | isIn frenchSyns = French+ | otherwise = English where isIn = elem (map toLower $ language c) getTranslator :: Language -> Translator getTranslator lang = case lang of- English -> id :: String -> String- Pirate -> pirate- French -> french+ Pirate -> pirate+ French -> french+ _ -> id :: String -> String dropComments :: String -> String dropComments = unlines . map (head . splitOn "--") . lines -processConfig :: ConfigInfo -> Config-processConfig raw = Config+processConfig :: FilePath -> FilePath -> ConfigInfo -> Config+processConfig dataDir configDir raw = Config { defaultUnit' = defaultUnit raw , defaultServingSize' = defaultServingSize raw- , recipesFile' = recipesFile raw+ , recipesFile' = dataDir </> recipesFile raw , translator = getTranslator $ getLang raw+ , dataDir = dataDir+ , configDir = configDir } +-- | The directory of the config.hs file. Its location is dictated by+-- <http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html XDG+-- Base Directory Specification>.+getConfigDir :: IO FilePath+getConfigDir = getXdgDirectory XdgConfig "herms"++-- | The directory of the recipes.herms file.+getDataDir :: IO FilePath+getDataDir = getXdgDirectory XdgData "herms"++-- | Create a directory if it doesn't exist, ensure it is readable,+-- writable, and executable.+directoryWithPermissions :: FilePath -> IO ()+directoryWithPermissions dir = do+ createDirectoryIfMissing True dir+ setPermissions dir $+ setOwnerReadable True $+ setOwnerWritable True $+ setOwnerExecutable True+ emptyPermissions++writeDefaultFile :: String -> FilePath -> IO ()+writeDefaultFile name path = do+ putStrLn $ "Couldn't find " ++ path+ templatePath <- getDataFileName name+ putStrLn $ "Copying default from " ++ templatePath+ copyFile templatePath path++-- | @readFileOrDefault attempts to read a file from a path. If that file+-- doesn't exist, it copies a default version that was installed by cabal+-- with @writeDefaultFile.+readFileOrDefault :: String -> FilePath -> IO String+readFileOrDefault name path = + (try (readFile path) :: IO (Either IOError String)) >>=+ \case+ Left _ -> writeDefaultFile name path >> readFile path+ Right content -> return content+ getConfig :: IO Config getConfig = do- fileName <- getDataFileName "config.hs"- contents <- readFile fileName++ -- Create configuration and data directories+ configDir <- getConfigDir+ dataDir <- getDataDir+ mapM_ directoryWithPermissions [configDir, dataDir]++ -- Try to read configuration file+ contents <- readFileOrDefault "config.hs" (configDir </> "config.hs")++ -- Parse the configuration let result = TR.readEither (dropComments contents) :: Either String ConfigInfo case result of- Left str -> throw ConfigParseError- Right r -> return (processConfig r)+ Left _ -> throw ConfigParseError+ Right r -> return (processConfig dataDir configDir r)
src/RichText.hs view
@@ -7,7 +7,6 @@ import Data.String import System.Console.ANSI import System.IO-import Debug.Trace class Text t where toStr :: Bool -> t -> String
src/UnitConversions.hs view
@@ -1,9 +1,7 @@ module UnitConversions where -import Data.List-import Data.Ratio import Data.Char (toLower)-import Types +import Types data Conversion = Metric | Imperial | None deriving (Show, Read, Eq) @@ -108,7 +106,7 @@ Just "pint" -> ingr{quantity = qty * 473, unit = "mL"} Just "quart" -> ingr{quantity = qty * 946, unit = "mL"} Just "gallon"-> ingr{quantity = qty * 3.785, unit = "L"}- Nothing -> ingr+ _ -> ingr where units = unit ingr qty = quantity ingr