packages feed

etc-0.0.0.2: src/System/Etc/Internal/Extra/EnvMisspell.hs

{-# LANGUAGE DeriveGeneric     #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
module System.Etc.Internal.Extra.EnvMisspell (
    EnvMisspell (..)
  , getEnvMisspellings
  , getEnvMisspellingsPure
  , renderEnvMisspellings
  , hPrintEnvMisspellings
  , reportEnvMisspellingWarnings
  ) where

import Protolude hiding ((<$>), (<>))

import Data.Vector        (Vector)
import System.Environment (getEnvironment)

import qualified Data.HashMap.Strict as HashMap
import qualified Data.Text           as Text
import qualified Data.Vector         as Vector
import qualified Text.EditDistance   as Distance

import System.Etc.Internal.Spec.Types
import Text.PrettyPrint.ANSI.Leijen

data EnvMisspell
  = EnvMisspell {
    currentText    :: Text
  , suggestionText :: Text
  }
  deriving (Show, Eq, Generic)

lookupSpecEnvKeys :: ConfigSpec a -> Vector Text
lookupSpecEnvKeys spec =
  let
    foldEnvSettings val acc =
      case val of
        ConfigValue _ sources ->
          maybe acc (`Vector.cons` acc) (envVar sources)
        SubConfig hsh ->
          HashMap.foldr foldEnvSettings acc hsh
  in
    foldEnvSettings (SubConfig $ specConfigValues spec) Vector.empty

{-|

-}
getEnvMisspellingsPure :: ConfigSpec a -> Vector Text -> Vector EnvMisspell
getEnvMisspellingsPure spec env = do
   specEnvName  <- lookupSpecEnvKeys spec
   currentEnvName <- env

   let
     distance =
       Distance.levenshteinDistance
           Distance.defaultEditCosts
           (Text.unpack specEnvName)
           (Text.unpack currentEnvName)

   guard (distance >= 1 && distance < 4)
   return $ EnvMisspell currentEnvName specEnvName

{-|

-}
getEnvMisspellings :: ConfigSpec a -> IO (Vector EnvMisspell)
getEnvMisspellings spec =
  getEnvironment
  & fmap (Vector.fromList . map (Text.pack . fst))
  & fmap (getEnvMisspellingsPure spec)

{-|

-}
renderEnvMisspellings :: Vector EnvMisspell -> Doc
renderEnvMisspellings misspells
  | Vector.null misspells =
      mempty
  | otherwise =
      misspells
      & Vector.map
            (\misspell ->
                text "WARNING: Environment variable `"
                <> text (Text.unpack $ currentText misspell)
                <> text "' found, perhaps you meant `"
                <> text (Text.unpack $ suggestionText misspell)
                <> text "'")
      & Vector.foldl' (<$>) mempty
      & (<$> mempty)
      & (<$> mempty)

{-|

-}
hPrintEnvMisspellings :: Handle -> Vector EnvMisspell -> IO ()
hPrintEnvMisspellings h =
  hPutDoc h . renderEnvMisspellings

{-|

-}
reportEnvMisspellingWarnings :: ConfigSpec a -> IO ()
reportEnvMisspellingWarnings spec =
  getEnvMisspellings spec >>=
  hPrintEnvMisspellings stderr