packages feed

figment-0.2.0.0: src/Figment/Metadata.hs

-- | Where configuration values come from.
module Figment.Metadata
  ( Metadata (..)
  , Source (..)
  , Pos (..)
  , named
  , fileMetadata
  , withSource
  , withInterpolater
  , withAddedAt
  , interpolate
  , renderSource
  , renderSrcLoc
  ) where

import Control.DeepSeq (NFData (..), rwhnf)
import Data.Text (Text)
import qualified Data.Text as Text
import GHC.Generics (Generic)
import GHC.Stack (SrcLoc (..))

import Figment.Path (Path, renderPath)
import Figment.Profile (Profile)

-- | A one-based line and column in a source text.
data Pos = Pos
  { posLine :: !Int
  , posColumn :: !Int
  }
  deriving stock (Eq, Ord, Show, Generic)
  deriving anyclass (NFData)

-- | The origin of a provider's data.
data Source
  = FileSource !FilePath   -- ^ a file on disk (resolved path)
  | CodeSource !SrcLoc     -- ^ values written in Haskell code
  | CustomSource !Text     -- ^ anything else (\"environment\", a URL, ...)
  deriving stock (Eq, Show, Generic)

instance NFData Source where
  rnf (FileSource p) = rnf p
  rnf (CodeSource l) = l `seq` ()
  rnf (CustomSource t) = rnf t

-- | Everything known about a provider.
--
-- 'metaInterpolate' turns a key path back into the provider's own
-- vocabulary, e.g. @db.port@ into @APP_DB\__PORT@ for environment
-- variables or @--db.port@ for CLI arguments; error messages and
-- missing-field hints use it so users are told what to /type/.
data Metadata = Metadata
  { metaName :: !Text
  , metaSource :: !(Maybe Source)
  , metaAddedAt :: !(Maybe SrcLoc)
  , metaInterpolate :: Profile -> Path -> Text
  }
  deriving stock (Generic)

-- | Equality ignores the interpolation function (functions have no
-- equality); two metadata are equal when they name the same source
-- added at the same place.
instance Eq Metadata where
  a == b =
    metaName a == metaName b
      && metaSource a == metaSource b
      && metaAddedAt a == metaAddedAt b

instance Show Metadata where
  showsPrec d m = showParen (d > 10) $
    showString "Metadata {metaName = " . shows (metaName m)
      . showString ", metaSource = " . shows (metaSource m)
      . showString ", metaAddedAt = " . shows (metaAddedAt m)
      . showString "}"

instance NFData Metadata where
  rnf (Metadata n s a i) = rnf n `seq` rnf s `seq` maybe () (`seq` ()) a `seq` rwhnf i

-- | Metadata with a name and the default interpolater (the rendered
-- key path).
named :: Text -> Metadata
named n = Metadata n Nothing Nothing (const renderPath)

-- | Metadata for a file-backed source.
fileMetadata :: Text -> FilePath -> Metadata
fileMetadata n p = withSource (FileSource p) (named n)

-- | Set the source.
withSource :: Source -> Metadata -> Metadata
withSource s m = m {metaSource = Just s}

-- | Replace the key-name renderer.
withInterpolater :: (Profile -> Path -> Text) -> Metadata -> Metadata
withInterpolater f m = m {metaInterpolate = f}

-- | Record where the provider was added.
withAddedAt :: SrcLoc -> Metadata -> Metadata
withAddedAt l m = m {metaAddedAt = Just l}

-- | The provider-native name of a key.
interpolate :: Metadata -> Profile -> Path -> Text
interpolate = metaInterpolate

-- | A short rendering of a source.
renderSource :: Source -> Text
renderSource (FileSource p) = Text.pack p
renderSource (CodeSource l) = renderSrcLoc l
renderSource (CustomSource t) = t

-- | @file:line:column@.
renderSrcLoc :: SrcLoc -> Text
renderSrcLoc l =
  Text.pack (srcLocFile l) <> ":" <> Text.pack (show (srcLocStartLine l))
    <> ":" <> Text.pack (show (srcLocStartCol l))