{-# LANGUAGE CPP #-}
{-# LANGUAGE Safe #-}
-- | Here's a simple example of a program that uses @envparse@'s parser:
--
-- @
-- module Main (main) where
--
-- import Control.Monad (unless)
-- import Env
--
-- data Hello = Hello { name :: String, quiet :: Bool }
--
-- hello :: IO Hello
-- hello = Env.'parse' ('Help.header' \"envparse example\") $
--   Hello \<$\> 'var' ('str' <=< 'nonempty') \"NAME\"  ('help' \"Target for the greeting\")
--         \<*\> 'switch'                 \"QUIET\" ('help' \"Whether to actually print the greeting\")
--
-- main :: IO ()
-- main = do
--   Hello {name, quiet} <- hello
--   unless quiet $
--     putStrLn (\"Hello, \" ++ name ++ \"!\")
-- @
--
-- The @NAME@ environment variable is mandatory and contains the name of the person to
-- greet. @QUIET@, on the other hand, is an optional boolean flag, false by default, that
-- decides whether the greeting should be silent.
--
-- If the @NAME@ variable is undefined in the environment then running the program will
-- result in the following help text:
--
-- @
-- envparse example
--
-- Available environment variables:
--
--   NAME                   Target for the greeting
--   QUIET                  Whether to actually print the
--                          greeting
--
-- Parsing errors:
--
--   NAME is unset
-- @
module Env
  ( parse
  , parseOr
  , Parser
  , Mod
  , Help.Info
  , Help.header
  , Help.desc
  , Help.footer
  , Help.handleError
  , Help.ErrorHandler
  , Help.defaultErrorHandler
  , prefixed
  , var
  , Var
  , Reader
  , str
  , nonempty
  , splitOn
  , auto
  , def
  , helpDef
  , flag
  , switch
  , Flag
  , HasHelp
  , help
  , HasKeep
  , keep
  , Help.helpDoc
  , Error(..)
  , Error.AsUnset(..)
  , Error.AsEmpty(..)
  , Error.AsUnread(..)
  -- * Re-exports
  -- $re-exports
  , optional, (<=<), (>=>), (<>), asum
  -- * Testing
  -- $testing
  , parsePure
  ) where

import           Control.Applicative
import           Control.Monad ((>=>), (<=<))
import           Data.Foldable (asum, for_)
#if __GLASGOW_HASKELL__ < 710
import           Data.Monoid (Monoid(..), (<>))
#else
import           Data.Monoid ((<>))
#endif
import           System.Environment (getEnvironment)
#if __GLASGOW_HASKELL__ >= 708
import           System.Environment (unsetEnv)
#endif
import           System.Exit (exitFailure)
import qualified System.IO as IO

import qualified Env.Internal.Help as Help
import           Env.Internal.Parser
import           Env.Internal.Error (Error)
import qualified Env.Internal.Error as Error

-- $re-exports
-- External functions that may be useful to the consumer of the library

-- $testing
-- Utilities to test—without dabbling in IO—that your parsers do
-- what you want them to do

-- | Parse the environment or die
--
-- Prints the help text and exits with @EXIT_FAILURE@ on encountering a parse error.
--
-- @
-- >>> parse ('Help.header' \"env-parse 0.2.0\") ('var' 'str' \"USER\" ('def' \"nobody\"))
-- @
parse :: (Help.Info Error -> Help.Info e) -> Parser e a -> IO a
parse m =
  fmap (either (\_ -> error "absurd") id) . parseOr die m

-- | Try to parse the environment
--
-- Use this if simply dying on failure (the behavior of 'parse') is inadequate for your needs.
parseOr :: (String -> IO a) -> (Help.Info Error -> Help.Info e) -> Parser e b -> IO (Either a b)
parseOr onFailure helpMod parser = do
  b <- fmap (parsePure parser) getEnvironment
#if __GLASGOW_HASKELL__ >= 708
  for_ b $ \_ ->
    eachUnsetVar parser unsetEnv
#endif
  traverseLeft (onFailure . Help.helpInfo (helpMod Help.defaultInfo) parser) b

die :: String -> IO a
die m =
  do IO.hPutStrLn IO.stderr m; exitFailure

traverseLeft :: Applicative f => (a -> f b) -> Either a t -> f (Either b t)
traverseLeft f =
  either (fmap Left . f) (pure . Right)