dirty-haskell.org: A Tool to Manage a Set of YAML Objects Representing Account Information — pwutil

It's not crazy — it's having fun with types.

A long time ago I wrote a bunch of scripts (first in bash, then zsh, and later perl) to manage a, sometimes encrypted, file containing account information I get asked to create and remember on a daily basis—accounts for shopping websites spring to mind.

pwutil is the newest iteration in this line of bunches of scripts.



pwget [<searchTerm> …]
Looks up and returns all accounts which contain any <searchTerm> anywhere in their representation — case insensitive.

pwadd [[--gen-<generator> [<generatorArgument> …] …] --] <identifier> [<attributeKey> <attributeValue> …]
Adds an account to the store — does not overwrite.


I shall document the project in a partial and file-wise fashion—amendments available on request.


├── default.nix
├── PWAdd.hs
├── PWGet.hs
├── PWUtil
│   ├── Extra
│   │   ├── PWGen.hs
│   │   └── SSHCmd.hs
│   ├── Types.hs
│   └── Util.hs
├── pwutil.hs
├── PWUtil.hs
└── pwutil.nix


is a nix expression allowing easy installation using the nix package manager. A ~/.nixpkgs/config.nix allowing one to do so might look thus:

  packageOverrides = pkgs: {
    pwutil = pkgs.callPackage /path/to/pwutil.nix {};

The derivation takes some arguments (write those in {} above):

main ? null
Overwrite pwutil.hs with a file path
with<Package> ? false
<Package> is one of Pwgen, or Ssh a the current time. If true wraps executables to have $PATH include <Package>.


Introducing PW (much as xmonad did with X) is an easy way to keep track of the PWConfig without resorting to function arguments. BackStore is our (new and improved) way of encapsulating store access in a totally customisable way—plain, which is essential readFile and writeFile as provided by ByteString, is provided for convenience in Util.hs. PWConfig most importantly contains a definition of generators (called by passing --gen-… to pwadd).

module PWUtil.Types (
  ) where

import Control.Monad.State
import qualified Data.Map as M
import Data.Yaml
import Data.ByteString

type PW = StateT PWConfig IO

data BackStore = BackStore
                 { readContents :: PW ByteString
                 , writeContents :: ByteString -> PW ()

data PWConfig = PWConfig
                { generators :: M.Map String Generator
                , backstore :: BackStore
type Generator = [String] -> PW Value


is, in a xmonad kind of way, the configuration file—the shipped default is reproduced below as a template for custom configs.

import PWUtil

import System.FilePath ((</>))
import System.Directory (getHomeDirectory)

main :: IO ()
main = do
  h <- getHomeDirectory
  runPW (emptyConfig { backstore = plain (h </> "accounts.yaml") }) pwutil