18 Commits

Author SHA1 Message Date
Jonathan Lamothe
c20fb30f5b version 0.15.0 2020-03-13 00:00:52 -04:00
Jonathan Lamothe
3c0e690ed3 Merge pull request #82 from mtlstats/del-player
delete player/goalie
2020-03-12 23:59:16 -04:00
Jonathan Lamothe
f37e231623 updated change log 2020-03-12 23:53:27 -04:00
Jonathan Lamothe
fbaf2a1e60 exit properly from delete menus 2020-03-12 23:52:40 -04:00
Jonathan Lamothe
65979329bd added player/goalie delete to menus 2020-03-12 23:42:40 -04:00
Jonathan Lamothe
ded019faac implemented deleting of goalies 2020-03-12 23:37:42 -04:00
Jonathan Lamothe
1322004d38 implemented player deletion 2020-03-12 23:19:17 -04:00
Jonathan Lamothe
2cb279e7e7 implemented dropNth 2020-03-12 22:41:28 -04:00
Jonathan Lamothe
7ca66ad801 Merge pull request #81 from mtlstats/page-break
add page break to report
2020-03-12 22:14:40 -04:00
Jonathan Lamothe
82544046ce add page break to report 2020-03-12 22:09:05 -04:00
Jonathan Lamothe
95c97d722e removed unnecessary dbFname config value 2020-03-12 12:47:38 -04:00
Jonathan Lamothe
0eb46cacce Merge pull request #80 from mtlstats/season-select
Select season database on startup
2020-03-12 03:27:04 -04:00
Jonathan Lamothe
25f887a5e8 don't call modify if database isn't changing 2020-03-12 03:19:27 -04:00
Jonathan Lamothe
7ba670948b updated change log 2020-03-12 02:46:44 -04:00
Jonathan Lamothe
ca06b0570e load and save databases properly 2020-03-12 02:44:41 -04:00
Jonathan Lamothe
1e8473538a prompt for database name 2020-03-11 03:56:58 -04:00
Jonathan Lamothe
87336dcd1d control flow branch for reading database 2020-03-11 03:20:38 -04:00
Jonathan Lamothe
ffa241c1f7 added dbName field to ProgState 2020-03-11 03:09:47 -04:00
17 changed files with 203 additions and 62 deletions

View File

@@ -1,5 +1,10 @@
# Changelog for mtlstats # Changelog for mtlstats
## 0.15.0
- Ask for database to load on start-up
- Add page break to report file
- Implemented player/goalie deletion
## 0.14.0 ## 0.14.0
- Fixed a bug that was causing shutouts to not be recorded - Fixed a bug that was causing shutouts to not be recorded
- Output report to a text file (report.txt) - Output report to a text file (report.txt)

View File

@@ -1,5 +1,5 @@
name: mtlstats name: mtlstats
version: 0.14.0 version: 0.15.0
github: "mtlstats/mtlstats" github: "mtlstats/mtlstats"
license: GPL-3 license: GPL-3
author: "Jonathan Lamothe" author: "Jonathan Lamothe"

View File

@@ -19,23 +19,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-} -}
{-# LANGUAGE ScopedTypeVariables #-}
module Mtlstats (initState, mainLoop) where module Mtlstats (initState, mainLoop) where
import Control.Exception (IOException, catch)
import Control.Monad (void) import Control.Monad (void)
import Control.Monad.Extra (whenM) import Control.Monad.Extra (whenM)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.State (get, gets) import Control.Monad.Trans.State (get, gets)
import Data.Aeson (decodeFileStrict) import Data.Maybe (fromJust)
import Data.Maybe (fromJust, fromMaybe)
import Lens.Micro ((&), (.~))
import System.EasyFile (getAppUserDataDirectory, (</>))
import qualified UI.NCurses as C import qualified UI.NCurses as C
import Mtlstats.Config
import Mtlstats.Control import Mtlstats.Control
import Mtlstats.Types import Mtlstats.Types
@@ -44,15 +36,7 @@ initState :: C.Curses ProgState
initState = do initState = do
C.setEcho False C.setEcho False
void $ C.setCursorMode C.CursorInvisible void $ C.setCursorMode C.CursorInvisible
db <- liftIO $ do return newProgState
dir <- getAppUserDataDirectory appName
let dbFile = dir </> dbFname
fromMaybe newDatabase <$> catch
(decodeFileStrict dbFile)
(\(_ :: IOException) -> return Nothing)
return
$ newProgState
& database .~ db
-- | Main program loop -- | Main program loop
mainLoop :: Action () mainLoop :: Action ()

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-} -}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase, ScopedTypeVariables #-}
module Mtlstats.Actions module Mtlstats.Actions
( startNewSeason ( startNewSeason
@@ -43,12 +43,14 @@ module Mtlstats.Actions
, backHome , backHome
, scrollUp , scrollUp
, scrollDown , scrollDown
, loadDatabase
, saveDatabase , saveDatabase
) where ) where
import Control.Exception (IOException, catch)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.State (gets, modify) import Control.Monad.Trans.State (gets, modify)
import Data.Aeson (encodeFile) import Data.Aeson (decodeFileStrict, encodeFile)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Lens.Micro ((^.), (&), (.~), (%~)) import Lens.Micro ((^.), (&), (.~), (%~))
import System.EasyFile import System.EasyFile
@@ -216,12 +218,27 @@ scrollUp = scrollOffset %~ max 0 . pred
scrollDown :: ProgState -> ProgState scrollDown :: ProgState -> ProgState
scrollDown = scrollOffset %~ succ scrollDown = scrollOffset %~ succ
-- | Loads the database
loadDatabase :: Action ()
loadDatabase = do
dbFile <- dbSetup
liftIO
(catch
(decodeFileStrict dbFile)
(\(_ :: IOException) -> return Nothing))
>>= mapM_ (modify . (database .~))
-- | Saves the database -- | Saves the database
saveDatabase :: String -> Action () saveDatabase :: Action ()
saveDatabase fn = do saveDatabase = do
db <- gets (^.database) db <- gets (^.database)
dbFile <- dbSetup
liftIO $ encodeFile dbFile db
dbSetup :: Action String
dbSetup = do
fn <- gets (^.dbName)
liftIO $ do liftIO $ do
dir <- getAppUserDataDirectory appName dir <- getAppUserDataDirectory appName
let dbFile = dir </> fn
createDirectoryIfMissing True dir createDirectoryIfMissing True dir
encodeFile dbFile db return $ dir </> fn ++ ".json"

View File

@@ -33,10 +33,6 @@ maxFunKeys = 9
appName :: String appName :: String
appName = "mtlstats" appName = "mtlstats"
-- | The database filename
dbFname :: String
dbFname = "database.json"
-- | The maximum number of assists -- | The maximum number of assists
maxAssists :: Int maxAssists :: Int
maxAssists = 2 maxAssists = 2

View File

@@ -39,7 +39,7 @@ import Mtlstats.Types
dispatch :: ProgState -> Controller dispatch :: ProgState -> Controller
dispatch s = case s^.progMode of dispatch s = case s^.progMode of
TitleScreen -> titleScreenC TitleScreen -> titleScreenC
MainMenu -> mainMenuC MainMenu -> mainMenuC s
NewSeason flag -> newSeasonC flag NewSeason flag -> newSeasonC flag
NewGame gs -> newGameC gs NewGame gs -> newGameC gs
EditMenu -> editMenuC EditMenu -> editMenuC
@@ -49,8 +49,10 @@ dispatch s = case s^.progMode of
EditGoalie egs -> editGoalieC egs EditGoalie egs -> editGoalieC egs
(EditStandings esm) -> editStandingsC esm (EditStandings esm) -> editStandingsC esm
mainMenuC :: Controller mainMenuC :: ProgState -> Controller
mainMenuC = Controller mainMenuC s = if null $ s^.dbName
then promptController getDBPrompt
else Controller
{ drawController = const $ drawMenu mainMenu { drawController = const $ drawMenu mainMenu
, handleController = menuHandler mainMenu , handleController = menuHandler mainMenu
} }

View File

@@ -23,10 +23,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Mtlstats.Control.EditGoalie (editGoalieC) where module Mtlstats.Control.EditGoalie (editGoalieC) where
import Control.Monad.Trans.State (gets, modify)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Lens.Micro ((^.)) import Lens.Micro ((^.), (.~), (%~))
import UI.NCurses as C import UI.NCurses as C
import Mtlstats.Actions
import Mtlstats.Handlers
import Mtlstats.Helpers.Goalie import Mtlstats.Helpers.Goalie
import Mtlstats.Menu import Mtlstats.Menu
import Mtlstats.Menu.EditGoalie import Mtlstats.Menu.EditGoalie
@@ -52,6 +55,7 @@ editC cb =
EGName -> nameC EGName -> nameC
EGYtd -> ytdMenuC EGYtd -> ytdMenuC
EGLifetime -> lifetimeMenuC EGLifetime -> lifetimeMenuC
EGDelete -> deleteC
EGYtdGames b -> ytdGamesC b EGYtdGames b -> ytdGamesC b
EGYtdMins b -> ytdMinsC b EGYtdMins b -> ytdMinsC b
EGYtdGoals b -> ytdGoalsC b EGYtdGoals b -> ytdGoalsC b
@@ -83,6 +87,38 @@ ytdMenuC _ = menuControllerWith header editGoalieYtdMenu
lifetimeMenuC :: Action () -> Controller lifetimeMenuC :: Action () -> Controller
lifetimeMenuC _ = menuControllerWith header editGoalieLtMenu lifetimeMenuC _ = menuControllerWith header editGoalieLtMenu
deleteC :: Action () -> Controller
deleteC _ = Controller
{ drawController = \s -> do
C.drawString $ let
hdr = fromMaybe [] $ do
gid <- s^.progMode.editGoalieStateL.egsSelectedGoalie
goalie <- nth gid $ s^.database.dbGoalies
Just $ "Goalie: " ++ goalieDetails goalie ++ "\n\n"
in hdr ++ "Are you sure you want to delete this goalie? (Y/N)"
return C.CursorInvisible
, handleController = \e -> do
case ynHandler e of
Just True -> do
gets (^.progMode.editGoalieStateL.egsSelectedGoalie) >>= mapM_
(\gid -> modify $ database.dbGoalies %~ dropNth gid)
modify edit
Just False -> modify $ progMode.editGoalieStateL.egsMode .~ EGMenu
Nothing -> return ()
return True
}
ytdGamesC :: Bool -> Action () -> Controller ytdGamesC :: Bool -> Action () -> Controller
ytdGamesC = curry $ promptController . ytdGamesC = curry $ promptController .
uncurry editGoalieYtdGamesPrompt uncurry editGoalieYtdGamesPrompt

View File

@@ -21,10 +21,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Mtlstats.Control.EditPlayer (editPlayerC) where module Mtlstats.Control.EditPlayer (editPlayerC) where
import Control.Monad.Trans.State (gets, modify)
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Lens.Micro ((^.)) import Lens.Micro ((^.), (.~), (%~))
import qualified UI.NCurses as C import qualified UI.NCurses as C
import Mtlstats.Actions
import Mtlstats.Handlers
import Mtlstats.Helpers.Player import Mtlstats.Helpers.Player
import Mtlstats.Menu import Mtlstats.Menu
import Mtlstats.Menu.EditPlayer import Mtlstats.Menu.EditPlayer
@@ -45,6 +48,7 @@ editPlayerC eps
EPPosition -> positionC EPPosition -> positionC
EPYtd -> ytdC EPYtd -> ytdC
EPLifetime -> lifetimeC EPLifetime -> lifetimeC
EPDelete -> deleteC
EPYtdGoals b -> ytdGoalsC b EPYtdGoals b -> ytdGoalsC b
EPYtdAssists b -> ytdAssistsC b EPYtdAssists b -> ytdAssistsC b
EPYtdPMin -> ytdPMinC EPYtdPMin -> ytdPMinC
@@ -74,6 +78,38 @@ ytdC _ = menuControllerWith header editPlayerYtdMenu
lifetimeC :: Action () -> Controller lifetimeC :: Action () -> Controller
lifetimeC _ = menuControllerWith header editPlayerLtMenu lifetimeC _ = menuControllerWith header editPlayerLtMenu
deleteC :: Action () -> Controller
deleteC _ = Controller
{ drawController = \s -> do
C.drawString $ let
hdr = fromMaybe [] $ do
pid <- s^.progMode.editPlayerStateL.epsSelectedPlayer
player <- nth pid $ s^.database.dbPlayers
Just $ "Player: " ++ playerDetails player ++ "\n\n"
in hdr ++ "Are you sure you want to delete this player? (Y/N)"
return C.CursorInvisible
, handleController = \e -> do
case ynHandler e of
Just True -> do
gets (^.progMode.editPlayerStateL.epsSelectedPlayer) >>= mapM_
(\pid -> modify $ database.dbPlayers %~ dropNth pid)
modify edit
Just False -> modify $ progMode.editPlayerStateL.epsMode .~ EPMenu
Nothing -> return ()
return True
}
ytdGoalsC :: Bool -> Action () -> Controller ytdGoalsC :: Bool -> Action () -> Controller
ytdGoalsC batchMode callback = promptController $ ytdGoalsC batchMode callback = promptController $
editPlayerYtdGoalsPrompt batchMode callback editPlayerYtdGoalsPrompt batchMode callback

View File

@@ -206,7 +206,7 @@ reportC = Controller
C.drawString $ unlines $ slice C.drawString $ unlines $ slice
(s^.scrollOffset) (s^.scrollOffset)
(fromInteger $ pred rows) (fromInteger $ pred rows)
(report (fromInteger $ pred cols) s) (displayReport (fromInteger $ pred cols) s)
return C.CursorInvisible return C.CursorInvisible
, handleController = \e -> do , handleController = \e -> do
case e of case e of
@@ -215,7 +215,7 @@ reportC = Controller
C.EventSpecialKey C.KeyHome -> modify $ scrollOffset .~ 0 C.EventSpecialKey C.KeyHome -> modify $ scrollOffset .~ 0
C.EventCharacter '\n' -> do C.EventCharacter '\n' -> do
get >>= liftIO . writeFile reportFilename . unlines . report reportCols get >>= liftIO . writeFile reportFilename . exportReport reportCols
modify backHome modify backHome
_ -> return () _ -> return ()

View File

@@ -45,7 +45,6 @@ import qualified UI.NCurses as C
import Mtlstats.Actions import Mtlstats.Actions
import qualified Mtlstats.Actions.NewGame.GoalieInput as GI import qualified Mtlstats.Actions.NewGame.GoalieInput as GI
import Mtlstats.Actions.EditStandings import Mtlstats.Actions.EditStandings
import Mtlstats.Config
import Mtlstats.Format import Mtlstats.Format
import Mtlstats.Types import Mtlstats.Types
import Mtlstats.Types.Menu import Mtlstats.Types.Menu
@@ -115,7 +114,7 @@ mainMenu = Menu "MASTER MENU" True
, MenuItem 'C' "EDIT MENU" $ , MenuItem 'C' "EDIT MENU" $
modify edit >> return True modify edit >> return True
, MenuItem 'E' "EXIT" $ , MenuItem 'E' "EXIT" $
saveDatabase dbFname >> return False saveDatabase >> return False
] ]
-- | The new season menu -- | The new season menu

View File

@@ -44,6 +44,7 @@ editGoalieMenu = Menu "EDIT GOALTENDER" () $ map
, ( 'D', "ACTIVE FLAG", toggleActive ) , ( 'D', "ACTIVE FLAG", toggleActive )
, ( 'E', "YTD STATS", set EGYtd ) , ( 'E', "YTD STATS", set EGYtd )
, ( 'F', "LIFETIME STATS", set EGLifetime ) , ( 'F', "LIFETIME STATS", set EGLifetime )
, ( 'G', "DELETE RECORD", set EGDelete )
, ( 'R', "RETURN TO EDIT MENU", edit ) , ( 'R', "RETURN TO EDIT MENU", edit )
] ]

View File

@@ -45,6 +45,7 @@ editPlayerMenu = Menu "EDIT PLAYER" () $ map
, ( 'E', "ACTIVE FLAG", toggleActive ) , ( 'E', "ACTIVE FLAG", toggleActive )
, ( 'F', "YTD STATS", set EPYtd ) , ( 'F', "YTD STATS", set EPYtd )
, ( 'G', "LIFETIME STATS", set EPLifetime ) , ( 'G', "LIFETIME STATS", set EPLifetime )
, ( 'H', "DELETE RECORD", set EPDelete )
, ( 'R', "RETURN TO EDIT MENU", edit ) , ( 'R', "RETURN TO EDIT MENU", edit )
] ]

View File

@@ -32,8 +32,10 @@ module Mtlstats.Prompt (
namePrompt, namePrompt,
numPrompt, numPrompt,
numPromptWithFallback, numPromptWithFallback,
dbNamePrompt,
selectPrompt, selectPrompt,
-- * Individual prompts -- * Individual prompts
getDBPrompt,
newSeasonPrompt, newSeasonPrompt,
playerNumPrompt, playerNumPrompt,
playerNamePrompt, playerNamePrompt,
@@ -168,24 +170,30 @@ numPromptWithFallback pStr fallback act = Prompt
, promptSpecialKey = const $ return () , promptSpecialKey = const $ return ()
} }
-- | Prompts for a database name
dbNamePrompt
:: String
-- ^ The prompt string
-> (String -> Action ())
-- ^ The callback to pass the result to
-> Prompt
dbNamePrompt pStr act = (strPrompt pStr act)
{ promptProcessChar = \ch -> if isAlphaNum ch || ch == '-'
then (++[toUpper ch])
else id
}
-- | Prompts the user for a filename to save a backup of the database -- | Prompts the user for a filename to save a backup of the database
-- to -- to
newSeasonPrompt :: Prompt newSeasonPrompt :: Prompt
newSeasonPrompt = prompt newSeasonPrompt = dbNamePrompt "Filename for new season: " $ \fn ->
{ promptProcessChar = \ch str -> if validChar ch
then str ++ [toUpper ch]
else str
}
where
prompt = strPrompt "Filename to save database: " $ \fn ->
if null fn if null fn
then modify backHome then modify backHome
else do else do
saveDatabase $ fn ++ ".json" saveDatabase
modify $ progMode .~ NewSeason True modify
$ (dbName .~ fn)
validChar = (||) <$> isAlphaNum <*> (=='-') . (progMode .~ NewSeason True)
-- | Builds a selection prompt -- | Builds a selection prompt
selectPrompt :: SelectParams a -> Prompt selectPrompt :: SelectParams a -> Prompt
@@ -224,6 +232,12 @@ selectPrompt params = Prompt
_ -> return () _ -> return ()
} }
-- | Prompts for the database to load
getDBPrompt :: Prompt
getDBPrompt = dbNamePrompt "Season database to load: " $ \fn -> do
modify $ dbName .~ fn
loadDatabase
-- | Prompts for a new player's number -- | Prompts for a new player's number
playerNumPrompt :: Prompt playerNumPrompt :: Prompt
playerNumPrompt = numPrompt "Player number: " $ playerNumPrompt = numPrompt "Player number: " $

View File

@@ -19,7 +19,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
-} -}
module Mtlstats.Report (report, gameDate) where module Mtlstats.Report (displayReport, exportReport, gameDate) where
import Data.List (sortOn) import Data.List (sortOn)
import qualified Data.Map as M import qualified Data.Map as M
@@ -34,21 +34,37 @@ import Mtlstats.Helpers.Player
import Mtlstats.Types import Mtlstats.Types
import Mtlstats.Util import Mtlstats.Util
-- | Generates the report -- | Generates the report displayed on screen
report displayReport
:: Int :: Int
-- ^ The number of columns for the report -- ^ The number of columns for the report
-> ProgState -> ProgState
-- ^ The program state -- ^ The program state
-> [String] -> [String]
displayReport width s
= report width s
++ [""]
++ lifetimeStatsReport width s
-- | Generates the report to be exported to file
exportReport
:: Int
-- ^ The number of columns in the report
-> ProgState
-- ^ The program state
-> String
exportReport width s
= unlines (report width s)
++ "\f"
++ unlines (lifetimeStatsReport width s)
report :: Int -> ProgState -> [String]
report width s report width s
= standingsReport width s = standingsReport width s
++ [""] ++ [""]
++ gameStatsReport width s ++ gameStatsReport width s
++ [""] ++ [""]
++ yearToDateStatsReport width s ++ yearToDateStatsReport width s
++ [""]
++ lifetimeStatsReport width s
standingsReport :: Int -> ProgState -> [String] standingsReport :: Int -> ProgState -> [String]
standingsReport width s = fromMaybe [] $ do standingsReport width s = fromMaybe [] $ do

View File

@@ -50,6 +50,7 @@ module Mtlstats.Types (
-- ** ProgState Lenses -- ** ProgState Lenses
database, database,
progMode, progMode,
dbName,
inputBuffer, inputBuffer,
scrollOffset, scrollOffset,
-- ** ProgMode Lenses -- ** ProgMode Lenses
@@ -233,6 +234,8 @@ data ProgState = ProgState
-- ^ The data to be saved -- ^ The data to be saved
, _progMode :: ProgMode , _progMode :: ProgMode
-- ^ The program's mode -- ^ The program's mode
, _dbName :: String
-- ^ The name of the database file
, _inputBuffer :: String , _inputBuffer :: String
-- ^ Buffer for user input -- ^ Buffer for user input
, _scrollOffset :: Int , _scrollOffset :: Int
@@ -375,6 +378,7 @@ data EditPlayerMode
| EPPosition | EPPosition
| EPYtd | EPYtd
| EPLifetime | EPLifetime
| EPDelete
| EPYtdGoals Bool | EPYtdGoals Bool
| EPYtdAssists Bool | EPYtdAssists Bool
| EPYtdPMin | EPYtdPMin
@@ -400,6 +404,7 @@ data EditGoalieMode
| EGName | EGName
| EGYtd | EGYtd
| EGLifetime | EGLifetime
| EGDelete
| EGYtdGames Bool | EGYtdGames Bool
| EGYtdMins Bool | EGYtdMins Bool
| EGYtdGoals Bool | EGYtdGoals Bool
@@ -781,6 +786,7 @@ newProgState :: ProgState
newProgState = ProgState newProgState = ProgState
{ _database = newDatabase { _database = newDatabase
, _progMode = TitleScreen , _progMode = TitleScreen
, _dbName = ""
, _inputBuffer = "" , _inputBuffer = ""
, _scrollOffset = 0 , _scrollOffset = 0
} }

View File

@@ -22,6 +22,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
module Mtlstats.Util module Mtlstats.Util
( nth ( nth
, modifyNth , modifyNth
, dropNth
, updateMap , updateMap
, slice , slice
, capitalizeName , capitalizeName
@@ -56,6 +57,18 @@ modifyNth n f = zipWith
(\i x -> if i == n then f x else x) (\i x -> if i == n then f x else x)
[0..] [0..]
-- | Attempt to drop the nth element from a list
dropNth
:: Int
-- ^ The index of the element to drop
-> [a]
-- ^ The list to be modified
-> [a]
-- ^ The modified list
dropNth n = foldr
(\(i, x) acc -> if i == n then acc else x : acc)
[] . zip [0..]
-- | Modify a value indexed by a given key in a map using a default -- | Modify a value indexed by a given key in a map using a default
-- initial value if not present -- initial value if not present
updateMap updateMap

View File

@@ -30,6 +30,7 @@ spec :: Spec
spec = describe "Mtlstats.Util" $ do spec = describe "Mtlstats.Util" $ do
nthSpec nthSpec
modifyNthSpec modifyNthSpec
dropNthSpec
updateMapSpec updateMapSpec
sliceSpec sliceSpec
capitalizeNameSpec capitalizeNameSpec
@@ -64,6 +65,20 @@ modifyNthSpec = describe "modifyNth" $ do
it "should not modify the value" $ it "should not modify the value" $
modifyNth (-1) succ list `shouldBe` [1, 2, 3] modifyNth (-1) succ list `shouldBe` [1, 2, 3]
dropNthSpec :: Spec
dropNthSpec = describe "dropNth" $ mapM_
(\(label, n, expected) ->
context label $
it ("should be " ++ show expected) $
dropNth n list `shouldBe` expected)
[ ( "out of bounds", 1, ["foo", "baz"] )
, ( "in bounds", 3, list )
]
where list = ["foo", "bar", "baz"]
updateMapSpec :: Spec updateMapSpec :: Spec
updateMapSpec = describe "updateMap" $ do updateMapSpec = describe "updateMap" $ do
let let