content: {-# OPTIONS_GHC -Wunused-imports #-}
  
  -- | Expand environment variables in strings
  module Agda.Utils.Environment
    ( EnvVars
    , expandEnvironmentVariables
    , expandEnvVarTelescope
    ) where
  
  import Data.Char
  import Data.Maybe
  import System.Environment
  import System.Directory ( getHomeDirectory )
  
  expandEnvironmentVariables :: String -> IO String
  expandEnvironmentVariables s = do
    env  <- getEnvironment
    home <- getHomeDirectory
    return $ expandVars home env s
  
  expandVars
    :: String              -- ^ Home directory.
    -> EnvVars             -- ^ Environment variable substitution map.
    -> String              -- ^ Input.
    -> String              -- ^ Output with variables and @~@ (home) substituted.
  expandVars home env s = concatMap repl $ tokens s
    where
      repl Home    = home ++ "/"
      repl (Var x) = fromMaybe "" $ lookup x env
      repl (Str s) = s
  
  -- | List of environment variable bindings.
  type EnvVars = [(String, String)]
  
  -- | Expand a telescope of environment variables
  --   (each value may refer to variables earlier in the list).
  expandEnvVarTelescope :: String -> EnvVars -> EnvVars
  expandEnvVarTelescope home = reverse . foldl  -- foldl reverses, so re-reverse afterwards
    (\ acc (var, val) -> (var, expandVars home acc val) : acc) []
  
  -- | Tokenization for environment variable substitution.
  data Token
    = Home        -- ^ @~@.
    | Var String  -- ^ @$VARIABLE@ or @${VARIABLE}@.
    | Str String  -- ^ Ordinary characters.
    deriving (Eq, Show)
  
  -- | Tokenize a string.
  --   The @~@ is recognized as @$HOME@ only at the beginning of the string.
  tokens :: String -> [Token]
  tokens = \case
    '~'  : '/' : s -> Home : tokens' s
    '\\' : '~' : s -> cons '~' $ tokens' s
    s -> tokens' s
    where
      tokens' :: String -> [Token]
      tokens' = \case
          '$' : '$' : s -> cons '$' $ tokens' s
          '$' : s@(c : _) | c == '_' || isAlpha c -> Var x : tokens' s'
            where
            (x, s') = span (\ c -> c == '_' || isAlphaNum c) s
          '$' : '{' : s ->
            case break (== '}') s of
              (x, '}' : s) -> Var x : tokens' s
              _            -> [Str $ "${" ++ s] -- abort on unterminated '{'
          c : s -> cons c $ tokens' s
          ""    -> []
      cons :: Char -> [Token] -> [Token]
      cons c (Str s : ts) = Str (c : s) : ts
      cons c ts           = Str [c] : ts
  