packages feed

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 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