6 Commits

6 changed files with 78 additions and 54 deletions

View File

@@ -32,6 +32,7 @@ dependencies:
- brick >= 2.1.1 && < 2.2
- vty >= 6.1 && < 6.2
- mtl >= 2.3.1 && < 2.4
- easy-file >= 0.2.5 && < 0.3
ghc-options:
- -Wall

View File

@@ -4,7 +4,7 @@ cabal-version: 2.2
--
-- see: https://github.com/sol/hpack
--
-- hash: fb2b45c8e1d5aead518c67637b3af5e78b55b8f0c5a95a34c20ca5d957527fa0
-- hash: d18a8e1efd32ff2d20b0b1f5ac8186be5242411bd72a6a017fd9f97d401a9836
name: passman
version: 0.3.1.1
@@ -46,6 +46,7 @@ library
, brick >=2.1.1 && <2.2
, bytestring >=0.11.4.0 && <0.12
, containers >=0.6.2.1 && <0.7
, easy-file >=0.2.5 && <0.3
, microlens >=0.4.11.2 && <0.5
, microlens-mtl >=0.2.0.3 && <0.3
, microlens-th >=0.4.3.6 && <0.5
@@ -70,6 +71,7 @@ executable passman
, brick >=2.1.1 && <2.2
, bytestring >=0.11.4.0 && <0.12
, containers >=0.6.2.1 && <0.7
, easy-file >=0.2.5 && <0.3
, microlens >=0.4.11.2 && <0.5
, microlens-mtl >=0.2.0.3 && <0.3
, microlens-th >=0.4.3.6 && <0.5
@@ -110,6 +112,7 @@ test-suite passman-test
, brick >=2.1.1 && <2.2
, bytestring >=0.11.4.0 && <0.12
, containers >=0.6.2.1 && <0.7
, easy-file >=0.2.5 && <0.3
, microlens >=0.4.11.2 && <0.5
, microlens-mtl >=0.2.0.3 && <0.3
, microlens-th >=0.4.3.6 && <0.5

View File

@@ -41,7 +41,7 @@ passmanApp = App
{ appDraw = drawFunc
, appChooseCursor = showFirstCursor
, appHandleEvent = eventHandler
, appStartEvent = return ()
, appStartEvent = loadDatabase
, appAttrMap = const $ attrMap (style 0) []
}

View File

@@ -22,25 +22,33 @@ License along with this program. If not, see
-}
{-# LANGUAGE OverloadedStrings #-}
module Password.App.Draw (drawFunc) where
import Brick (Widget, txt, vBox)
import Brick.Forms (renderForm)
import Brick (Widget, emptyWidget, txt, vBox)
import Brick.Forms (Form, formState, renderForm)
import Data.Text (Text)
import Lens.Micro ((^.))
import Password.App.Types
-- | Renders the application view
drawFunc :: AppState -> [Widget ResName]
drawFunc s = case s^.appMode of
InitMode is -> drawPassForm is
drawFunc s = maybe [emptyWidget] drawPassForm $ s^.passForm
drawPassForm :: InitState -> [Widget ResName]
drawPassForm is =
drawPassForm :: Form (Text, Text) () ResName -> [Widget ResName]
drawPassForm f =
[ vBox
[ renderForm $ is^.setPassForm
, txt $ is^.spfError
[ renderForm f
, txt $ pfText $ formState f
]
]
pfText :: (Text, Text) -> Text
pfText (pass, conf)
| pass == "" = "Password cannot be blank."
| pass /= conf = "Passwords do not match."
| otherwise = ""
--jl

View File

@@ -24,7 +24,7 @@ License along with this program. If not, see
{-# LANGUAGE LambdaCase, OverloadedStrings #-}
module Password.App.Event (eventHandler) where
module Password.App.Event (eventHandler, loadDatabase) where
import Brick (BrickEvent (VtyEvent), EventM, halt)
import Brick.Forms (handleFormEvent)
@@ -39,13 +39,24 @@ import Brick.Keybindings
, onEvent
)
import Control.Monad (unless)
import Control.Monad.State.Class (gets)
import Control.Monad.IO.Class (liftIO)
import Control.Monad.State.Class (gets, put)
import Data.Aeson (decodeFileStrict)
import Graphics.Vty.Input.Events (Event (EvKey))
import Lens.Micro ((^.))
import Lens.Micro (each, (^.))
import Lens.Micro.Mtl (zoom)
import System.EasyFile
( createDirectoryIfMissing
, doesFileExist
, getAppUserDataDirectory
, (</>)
)
import Password.App.Types
dbFile :: String
dbFile = "database.json"
data KEventID = QuitKE deriving (Eq, Ord, Show)
-- | The main event handler
@@ -55,10 +66,20 @@ eventHandler e@(VtyEvent (EvKey k m)) = do
handleKey disp k m >>= flip unless (fallbackHandler e)
eventHandler e = fallbackHandler e
loadDatabase :: EventM ResName AppState ()
loadDatabase = zoom database $ liftIO
( do
dir <- mkAppDir
let fn = dir </> dbFile
doesFileExist fn >>= \case
True -> decodeFileStrict fn
False -> return Nothing
) >>= mapM_ put
fallbackHandler :: BrickEvent ResName () -> EventM ResName AppState ()
fallbackHandler e = gets (^.appMode) >>= \case
InitMode _ -> zoom (appMode.initState.setPassForm) $
handleFormEvent e
fallbackHandler e = gets (^.passForm) >>= \case
Just _ -> zoom (passForm.each) $ handleFormEvent e
Nothing -> return ()
getKeyDispatcher
:: AppState
@@ -76,4 +97,10 @@ getKeyDispatcher s = either (error "can't build dispatcher") id $
keyBindingsFor :: AppState -> [(KEventID, [Binding])]
keyBindingsFor = const [(QuitKE, [ctrl 'c'])]
mkAppDir :: IO FilePath
mkAppDir = do
path <- getAppUserDataDirectory "passman"
createDirectoryIfMissing True path
return path
--jl

View File

@@ -27,18 +27,13 @@ License along with this program. If not, see
module Password.App.Types (
-- * Types
AppState (..),
AppMode (..),
InitState (..),
ResName (..),
-- * Lenses
-- ** AppState
randGen,
appMode,
-- ** AppMode
initState,
-- ** InitState
setPassForm,
spfError,
database,
mainPass,
passForm,
-- * Constructors
mkInitialState,
) where
@@ -51,26 +46,21 @@ import Lens.Micro (_1, _2)
import Lens.Micro.TH (makeLenses)
import System.Random (StdGen, initStdGen)
import Password
-- | The application state
data AppState = AppState
{ _randGen :: StdGen
-- ^ The random number generator
, _appMode :: AppMode
, _database :: PWDatabase
-- ^ The password database
, _mainPass :: String
-- ^ The main password
, _passForm :: Maybe PassForm
}
-- | The applicaiton's mode
newtype AppMode = InitMode
{ _initState :: InitState
-- ^ Initialization state
}
-- | Application initialization state
data InitState = InitState
{ _setPassForm :: Form (Text, Text) () ResName
-- ^ password form
, _spfError :: Text
-- ^ error message
}
-- | A password form (with confirmation)
type PassForm = Form (Text, Text) () ResName
-- | Resource identifier
data ResName
@@ -78,27 +68,22 @@ data ResName
| ConfField
deriving (Eq, Ord, Show)
concat <$> mapM makeLenses
[ ''AppState
, ''AppMode
, ''InitState
]
makeLenses ''AppState
-- | Builds an initial state
mkInitialState :: MonadIO m => m AppState
mkInitialState = AppState
<$> initStdGen
<*> return (InitMode newInitState)
<*> return newPWDatabase
<*> return ""
<*> return (Just newPassForm)
-- | New `InitState` value
newInitState :: InitState
newInitState = InitState
( newForm
[ (txt "Master password: " <+>) @@= editPasswordField _1 PassField
, (txt "Confirm password: " <+>) @@= editPasswordField _2 ConfField
]
("", "")
)
""
-- | Constructs a blank password form
newPassForm :: PassForm
newPassForm = newForm
[ (txt "Master password: " <+>) @@= editPasswordField _1 PassField
, (txt "Confirm password: " <+>) @@= editPasswordField _2 ConfField
]
("", "")
--jl