packages feed

etc-0.3.2.0: src/System/Etc/Internal/Extra/Printer.hs

{-# OPTIONS_GHC -fno-warn-deprecations #-}
{-# LANGUAGE NamedFieldPuns    #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
module System.Etc.Internal.Extra.Printer (
    renderConfig
  , renderConfigColor
  , printPrettyConfig
  , hPrintPrettyConfig
  ) where

import           RIO              hiding ((<>))
import qualified RIO.HashMap      as HashMap
import           RIO.List         (intersperse)
import           RIO.List.Partial (maximum)
import qualified RIO.Set          as Set
import qualified RIO.Text         as Text

import qualified Data.Aeson as JSON

import Text.PrettyPrint.ANSI.Leijen

import System.Etc.Internal.Types

renderJsonValue :: Text -> Value JSON.Value -> (Doc, Int)
renderJsonValue key value' = case value' of
  Plain JSON.Null         -> (text "null", 4)

  Plain (JSON.String str) -> (text $ Text.unpack str, Text.length str)

  Plain (JSON.Number scientific) ->
    let number = show scientific in (text number, length number)
  Plain     (JSON.Bool bool') -> if bool' then (text "true", 5) else (text "false", 5)
  Sensitive _                 -> (text "<<sensitive>>", 13)
  _ ->
    value'
      & tshow
      & ("Invalid configuration value creation " `mappend`)
      & InvalidConfiguration (Just key)
      & show
      & error

data ColorFn
  = ColorFn {
    greenColor :: !(Doc -> Doc)
  , blueColor  :: !(Doc -> Doc)
  }

renderConfig' :: ColorFn -> Config -> Doc
renderConfig' ColorFn { greenColor, blueColor } (Config configValue0) =
  let
    brackets' = enclose (lbracket <> space) (space <> rbracket)

    renderSource :: Text -> ConfigSource -> ((Doc, Int), Doc)
    renderSource key source' = case source' of
      Default value' -> (renderJsonValue key value', brackets' (fill 10 (text "Default")))

      File _index fileSource value' -> case fileSource of
        FilePathSource filepath' ->
          ( renderJsonValue key value'
          , brackets' (fill 10 (text "File:" <+> text (Text.unpack filepath')))
          )
        EnvVarFileSource envVar filepath' ->
          ( renderJsonValue key value'
          , brackets'
            (fill
              10
              (text "File:" <+> text (Text.unpack envVar) <> "=" <> text
                (Text.unpack filepath')
              )
            )
          )

      Env varname value' ->
        ( renderJsonValue key value'
        , brackets' (fill 10 (text "Env:" <+> text (Text.unpack varname)))
        )

      Cli value' -> (renderJsonValue key value', brackets' (fill 10 (text "Cli")))

      None       -> ((mempty, 0), mempty)

    renderSources :: Text -> [ConfigSource] -> Doc
    renderSources keys sources0 =
      let
        -- NOTE: I've already checked for the list to not be empty,
        -- so is safe to do this destructuring here
        sources@(((selValueDoc, _), selSourceDoc) : others) =
          map (renderSource keys) sources0

        -- NOTE: I've already checked for the list to not be empty,
        -- so is safe to use partial function maximum here
        fillingWidth  = sources & map (snd . fst) & maximum & max 10

        selectedValue = [greenColor $ fill fillingWidth selValueDoc <+> selSourceDoc]

        otherValues   = map
          (\((valueDoc, _), sourceDoc) -> fill fillingWidth valueDoc <+> sourceDoc)
          others
      in
        selectedValue & flip mappend otherValues & vcat & indent 2

    configEntryRenderer :: [Text] -> [Doc] -> Text -> ConfigValue -> [Doc]
    configEntryRenderer keys resultDoc configKey configValue =
      resultDoc `mappend` loop (configKey : keys) configValue

    loop keys configValue = case configValue of
      SubConfig subConfigm ->
        HashMap.foldlWithKey' (configEntryRenderer keys) mempty subConfigm

      ConfigValue sources0 ->
        let
          configKey = keys & reverse & Text.intercalate "."

          sources   = Set.toDescList sources0
        in
          if null sources
          then
            []
          else
            [blueColor (text (Text.unpack configKey)) <$$> renderSources configKey sources]
  in
    loop [] configValue0 & intersperse (linebreak <> linebreak) & hcat & (<> linebreak)


renderConfigColor :: Config -> Doc
renderConfigColor = renderConfig' ColorFn {greenColor = green, blueColor = blue}

renderConfig :: Config -> Doc
renderConfig = renderConfig' ColorFn {greenColor = id, blueColor = id}

printPrettyConfig :: Config -> IO ()
printPrettyConfig = putDoc . renderConfig

hPrintPrettyConfig :: Handle -> Config -> IO ()
hPrintPrettyConfig handle' = hPutDoc handle' . renderConfig