    -----------------------------------------------------------------------------
    -- |
    -- Module      :  XMonad.Prompt.Pass
    -- Copyright   :  (c) 2014 Igor Babuschkin, Antoine R. Dumont
    -- License     :  BSD3-style (see LICENSE)
    --
    -- Maintainer  :  Antoine R. Dumont <eniotna.t@gmail.com>
    -- Stability   :  unstable
    -- Portability :  unportable
    --
    -- This module provides 5 <XMonad.Prompt>s to ease password
    -- manipulation (generate, read, edit, remove):
    --
    -- - two to lookup passwords in the password-store; one of which
    --   copies to the clipboard, and the other uses @xdotool@ to type the
    --   password directly.
    --
    -- - one to generate a password for a given password label that the
    --   user inputs.
    --
    -- - one to edit a password for a given password label that the user
    --   inputs.
    --
    -- - one to delete a stored password for a given password label that
    --   the user inputs.
    --
    -- All those prompts benefit from the completion system provided by
    -- the module <XMonad.Prompt>.
    --
    -- The password store is setup through an environment variable
    -- PASSWORD_STORE_DIR, or @$HOME\/.password-store@ if it is unset.
    -- The editor is determined from the environment variable EDITOR.
    --
    -- Source:
    --
    -- - The <https://www.passwordstore.org/ password store>
    --   implementation is <http://git.zx2c4.com/password-store here>.
    --
    -- - Inspired by <http://babushk.in/posts/combining-xmonad-and-pass.html>
    --
    -----------------------------------------------------------------------------

    module XMonad.Prompt.Pass (
                                -- * Usage
                                -- $usage
                                  passPrompt
                                , passOTPPrompt
                                , passGeneratePrompt
                                , passGenerateAndCopyPrompt
                                , passRemovePrompt
                                , passEditPrompt
                                , passTypePrompt
                                ) where

    import XMonad.Core
    import XMonad.Prompt ( XPrompt
                         , showXPrompt
                         , commandToComplete
                         , nextCompletion
                         , getNextCompletion
                         , XPConfig
                         , mkXPrompt
                         , searchPredicate)
    import System.Directory (getHomeDirectory)
    import System.FilePath (takeExtension, dropExtension, combine)
    import System.Posix.Env (getEnv)
    import XMonad.Util.Run (runProcessWithInput)

    -- $usage
    -- You can use this module with the following in your @~\/.xmonad\/xmonad.hs@:
    --
    -- > import XMonad.Prompt.Pass
    --
    -- Then add a keybinding for 'passPrompt', 'passGeneratePrompt',
    -- 'passRemovePrompt', 'passEditPrompt' or 'passTypePrompt':
    --
    -- >   , ((modMask , xK_p)                              , passPrompt xpconfig)
    -- >   , ((modMask .|. controlMask, xK_p)               , passGeneratePrompt xpconfig)
    -- >   , ((modMask .|. shiftMask, xK_p)                 , passEditPrompt xpconfig)
    -- >   , ((modMask .|. controlMask  .|. shiftMask, xK_p), passRemovePrompt xpconfig)
    --
    -- For detailed instructions on:
    --
    -- - editing your key bindings, see "XMonad.Doc.Extending#Editing_key_bindings".
    --
    -- - how to setup the password store, see <http://git.zx2c4.com/password-store/about/>
    --

    type Predicate = String -> String -> Bool

    getPassCompl :: [String] -> Predicate -> String -> IO [String]
    getPassCompl :: [String] -> Predicate -> String -> IO [String]
    getPassCompl compls :: [String]
    compls p :: Predicate
    p s :: String
    s = [String] -> IO [String]
    forall (m :: * -> *) a. Monad m => a -> m a
    return ([String] -> IO [String]) -> [String] -> IO [String]
    forall a b. (a -> b) -> a -> b
    $ (String -> Bool) -> [String] -> [String]
    forall a. (a -> Bool) -> [a] -> [a]
    filter (Predicate
    p String
    s) [String]
    compls

    type PromptLabel = String

    newtype Pass = Pass PromptLabel

    instance XPrompt Pass where
      showXPrompt :: Pass -> String
    showXPrompt       (Pass prompt :: String
    prompt) = String
    prompt String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ ": "
      commandToComplete :: Pass -> String -> String
    commandToComplete _ c :: String
    c           = String
    c
      nextCompletion :: Pass -> String -> [String] -> String
    nextCompletion      _           = String -> [String] -> String
    getNextCompletion

    -- | Default password store folder in $HOME/.password-store
    --
    passwordStoreFolderDefault :: String -> String
    passwordStoreFolderDefault :: String -> String
    passwordStoreFolderDefault home :: String
    home = String -> String -> String
    combine String
    home ".password-store"

    -- | Compute the password store's location.
    -- Use the PASSWORD_STORE_DIR environment variable to set the password store.
    -- If empty, return the password store located in user's home.
    --
    passwordStoreFolder :: IO String
    passwordStoreFolder :: IO String
    passwordStoreFolder =
      String -> IO (Maybe String)
    getEnv "PASSWORD_STORE_DIR" IO (Maybe String) -> (Maybe String -> IO String) -> IO String
    forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
    >>= Maybe String -> IO String
    computePasswordStoreDir
      where computePasswordStoreDir :: Maybe String -> IO String
    computePasswordStoreDir Nothing         = (String -> String) -> IO String -> IO String
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap String -> String
    passwordStoreFolderDefault IO String
    getHomeDirectory
            computePasswordStoreDir (Just storeDir :: String
    storeDir) = String -> IO String
    forall (m :: * -> *) a. Monad m => a -> m a
    return String
    storeDir

    -- | A pass prompt factory
    --
    mkPassPrompt :: PromptLabel -> (String -> X ()) -> XPConfig -> X ()
    mkPassPrompt :: String -> (String -> X ()) -> XPConfig -> X ()
    mkPassPrompt promptLabel :: String
    promptLabel passwordFunction :: String -> X ()
    passwordFunction xpconfig :: XPConfig
    xpconfig = do
      [String]
    passwords <- IO [String] -> X [String]
    forall (m :: * -> *) a. MonadIO m => IO a -> m a
    io (IO String
    passwordStoreFolder IO String -> (String -> IO [String]) -> IO [String]
    forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
    >>= String -> IO [String]
    getPasswords)
      Pass
    -> XPConfig -> (String -> IO [String]) -> (String -> X ()) -> X ()
    forall p.
    XPrompt p =>
    p
    -> XPConfig -> (String -> IO [String]) -> (String -> X ()) -> X ()
    mkXPrompt (String -> Pass
    Pass String
    promptLabel) XPConfig
    xpconfig ([String] -> Predicate -> String -> IO [String]
    getPassCompl [String]
    passwords (Predicate -> String -> IO [String])
    -> Predicate -> String -> IO [String]
    forall a b. (a -> b) -> a -> b
    $ XPConfig -> Predicate
    searchPredicate XPConfig
    xpconfig) String -> X ()
    passwordFunction

    -- | A prompt to retrieve a password from a given entry.
    --
    passPrompt :: XPConfig -> X ()
    passPrompt :: XPConfig -> X ()
    passPrompt = String -> (String -> X ()) -> XPConfig -> X ()
    mkPassPrompt "Select password" String -> X ()
    selectPassword

    -- | A prompt to retrieve a OTP from a given entry.
    --
    passOTPPrompt :: XPConfig -> X ()
    passOTPPrompt :: XPConfig -> X ()
    passOTPPrompt = String -> (String -> X ()) -> XPConfig -> X ()
    mkPassPrompt "Select OTP" String -> X ()
    selectOTP

    -- | A prompt to generate a password for a given entry.
    -- This can be used to override an already stored entry.
    -- (Beware that no confirmation is asked)
    --
    passGeneratePrompt :: XPConfig -> X ()
    passGeneratePrompt :: XPConfig -> X ()
    passGeneratePrompt = String -> (String -> X ()) -> XPConfig -> X ()
    mkPassPrompt "Generate password" String -> X ()
    generatePassword

    -- | A prompt to generate a password for a given entry and immediately copy it
    -- to the clipboard.  This can be used to override an already stored entry.
    -- (Beware that no confirmation is asked)
    --
    passGenerateAndCopyPrompt :: XPConfig -> X ()
    passGenerateAndCopyPrompt :: XPConfig -> X ()
    passGenerateAndCopyPrompt = String -> (String -> X ()) -> XPConfig -> X ()
    mkPassPrompt "Generate and copy password" String -> X ()
    generateAndCopyPassword

    -- | A prompt to remove a password for a given entry.
    -- (Beware that no confirmation is asked)
    --
    passRemovePrompt :: XPConfig -> X ()
    passRemovePrompt :: XPConfig -> X ()
    passRemovePrompt = String -> (String -> X ()) -> XPConfig -> X ()
    mkPassPrompt "Remove password" String -> X ()
    removePassword

    -- | A prompt to type in a password for a given entry.
    -- This doesn't touch the clipboard.
    --
    passTypePrompt :: XPConfig -> X ()
    passTypePrompt :: XPConfig -> X ()
    passTypePrompt = String -> (String -> X ()) -> XPConfig -> X ()
    mkPassPrompt "Type password" String -> X ()
    typePassword

    -- | A prompt to edit a given entry.
    -- This doesn't touch the clipboard.
    --
    passEditPrompt :: XPConfig -> X ()
    passEditPrompt :: XPConfig -> X ()
    passEditPrompt = String -> (String -> X ()) -> XPConfig -> X ()
    mkPassPrompt "Edit password" String -> X ()
    editPassword

    -- | Select a password.
    --
    selectPassword :: String -> X ()
    selectPassword :: String -> X ()
    selectPassword passLabel :: String
    passLabel = String -> X ()
    forall (m :: * -> *). MonadIO m => String -> m ()
    spawn (String -> X ()) -> String -> X ()
    forall a b. (a -> b) -> a -> b
    $ "pass --clip \"" String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String -> String
    escapeQuote String
    passLabel String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ "\""

    -- | Select a OTP.
    --
    selectOTP :: String -> X ()
    selectOTP :: String -> X ()
    selectOTP passLabel :: String
    passLabel = String -> X ()
    forall (m :: * -> *). MonadIO m => String -> m ()
    spawn (String -> X ()) -> String -> X ()
    forall a b. (a -> b) -> a -> b
    $ "pass otp --clip \"" String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String -> String
    escapeQuote String
    passLabel String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ "\""

    -- | Generate a 30 characters password for a given entry.
    -- If the entry already exists, it is updated with a new password.
    --
    generatePassword :: String -> X ()
    generatePassword :: String -> X ()
    generatePassword passLabel :: String
    passLabel = String -> X ()
    forall (m :: * -> *). MonadIO m => String -> m ()
    spawn (String -> X ()) -> String -> X ()
    forall a b. (a -> b) -> a -> b
    $ "pass generate --force \"" String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String -> String
    escapeQuote String
    passLabel String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ "\" 30"

    -- | Generate a 30 characters password for a given entry.
    -- If the entry already exists, it is updated with a new password.
    -- After generating the password, it is copied to the clipboard.
    --
    generateAndCopyPassword :: String -> X ()
    generateAndCopyPassword :: String -> X ()
    generateAndCopyPassword passLabel :: String
    passLabel = String -> X ()
    forall (m :: * -> *). MonadIO m => String -> m ()
    spawn (String -> X ()) -> String -> X ()
    forall a b. (a -> b) -> a -> b
    $ "pass generate --force -c \"" String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String -> String
    escapeQuote String
    passLabel String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ "\" 30"

    -- | Remove a password stored for a given entry.
    --
    removePassword :: String -> X ()
    removePassword :: String -> X ()
    removePassword passLabel :: String
    passLabel = String -> X ()
    forall (m :: * -> *). MonadIO m => String -> m ()
    spawn (String -> X ()) -> String -> X ()
    forall a b. (a -> b) -> a -> b
    $ "pass rm --force \"" String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String -> String
    escapeQuote String
    passLabel String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ "\""

    -- | Edit a password stored for a given entry.
    --
    editPassword :: String -> X ()
    editPassword :: String -> X ()
    editPassword passLabel :: String
    passLabel = String -> X ()
    forall (m :: * -> *). MonadIO m => String -> m ()
    spawn (String -> X ()) -> String -> X ()
    forall a b. (a -> b) -> a -> b
    $ "pass edit \"" String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String -> String
    escapeQuote String
    passLabel String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ "\""

    -- | Type a password stored for a given entry using xdotool.
    --
    typePassword :: String -> X ()
    typePassword :: String -> X ()
    typePassword passLabel :: String
    passLabel = String -> X ()
    forall (m :: * -> *). MonadIO m => String -> m ()
    spawn (String -> X ()) -> String -> X ()
    forall a b. (a -> b) -> a -> b
    $ "pass \"" String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ String -> String
    escapeQuote String
    passLabel
      String -> String -> String
    forall a. [a] -> [a] -> [a]
    ++ "\"|head -n1|tr -d '\n'|xdotool type --clearmodifiers --file -"

    escapeQuote :: String -> String
    escapeQuote :: String -> String
    escapeQuote = (Char -> String) -> String -> String
    forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
    concatMap Char -> String
    escape
      where escape :: Char -> String
            escape :: Char -> String
    escape '"' = ['\\', '\"']
            escape x :: Char
    x = Char -> String
    forall (m :: * -> *) a. Monad m => a -> m a
    return Char
    x

    -- | Retrieve the list of passwords from the password store 'passwordStoreDir
    getPasswords :: FilePath -> IO [String]
    getPasswords :: String -> IO [String]
    getPasswords passwordStoreDir :: String
    passwordStoreDir = do
      String
    files <- String -> [String] -> String -> IO String
    forall (m :: * -> *).
    MonadIO m =>
    String -> [String] -> String -> m String
    runProcessWithInput "find" [
        "-L", -- Traverse symlinks
        String
    passwordStoreDir,
        "-type", "f",
        "-name", "*.gpg",
        "-printf", "%P\n"] []
      [String] -> IO [String]
    forall (m :: * -> *) a. Monad m => a -> m a
    return ([String] -> IO [String])
    -> ([String] -> [String]) -> [String] -> IO [String]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (String -> String) -> [String] -> [String]
    forall a b. (a -> b) -> [a] -> [b]
    map String -> String
    removeGpgExtension ([String] -> IO [String]) -> [String] -> IO [String]
    forall a b. (a -> b) -> a -> b
    $ String -> [String]
    lines String
    files

    removeGpgExtension :: String -> String
    removeGpgExtension :: String -> String
    removeGpgExtension file :: String
    file | String -> String
    takeExtension String
    file Predicate
    forall a. Eq a => a -> a -> Bool
    == ".gpg" = String -> String
    dropExtension String
    file
                            | Bool
    otherwise                    = String
    file