packages feed

nyan-interpolation-core-0.9: src/Text/Interpolation/Nyan/Core/Internal/Base.hs

-- SPDX-FileCopyrightText: 2022 Serokell <https://serokell.io/>
--
-- SPDX-License-Identifier: MPL-2.0

{-# LANGUAGE DerivingVia #-}

module Text.Interpolation.Nyan.Core.Internal.Base where

import Data.Monoid (Endo (..))
import Data.Text (Text)
import Language.Haskell.TH (ExpQ)

{- $setup

> import Data.Text
> import Data.Text.Lazy
-}


-- * Interpolation data

-- | Information about single piece that is to be interpolated.
data IntData = IntData
  { idMode :: Text
    -- ^ How to use the value in the braces.
    -- This is some text before the brances, usually one letter or nothing.
  , idCode :: Text
    -- ^ The inserted code. This is what appears in braces.
  } deriving stock (Show, Eq)

-- | Piece of interpolation string.
data ParsedIntPiece
  = PipString Text
    -- ^ Mere text.
  | PipNewline Text
    -- ^ Some line feed.
    -- This must be preferred over 'PipString'.
  | PipLeadingWs Word
    -- ^ Whitespaces at the beginning of the line.
    -- This must be preferred over 'PipString'.
  | PipEmptyLine
    -- ^ Line without any text. The line feed is not included here.
    -- This must be preferred over 'PipLeadingWs'.
  | PipInt IntData
    -- ^ Interpolator piece.
  deriving stock (Show, Eq)

type ParsedInterpolatedString = [ParsedIntPiece]

-- | Finalized piece of interpolation string.
data IntPiece
  = IpString Text
    -- ^ Mere text.
  | IpInt IntData
    -- ^ Interpolator piece.
  deriving stock (Show, Eq)

type InterpolatedString = [IntPiece]

-- * Switches

-- | Return type of the interpolator.
data ReturnType
  = AnyFromBuilder
    -- ^ @FromBuilder a => a@
  | ConcreteText
    -- ^ 'Data.Text.Text'
  | ConcreteLText
    -- ^ 'Data.Text.Lazy.Text'
  | ConcreteBuilder
    -- ^ 'Data.Text.Lazy.Builder.Builder'
  deriving stock (Show, Eq)

-- | Requested preview level.
data PreviewLevel
  = PreviewNone
    -- ^ Do nothing special.
  | PreviewExact
    -- ^ Print the resulting text as-is (without substitutions).
  | PreviewInvisible
    -- ^ Print the text, replacing invisible characters with special symbols.
  deriving stock (Show, Eq, Enum, Bounded)

-- | All switches options.
data SwitchesOptions = SwitchesOptions
  { spacesTrimming          :: Bool
  , indentationStripping    :: Bool
  , leadingNewlineStripping :: Bool
  , trailingSpacesStripping :: Bool
  , returnType              :: ReturnType
  , reducedNewlines         :: Bool
  , monadic                 :: Bool
  , previewLevel            :: PreviewLevel
  } deriving stock (Show, Eq)

-- | Default switches options set in the interpolator, those that are used
-- in @[int||...|]@.
--
-- When no default value for a switch is specified, this switch is left
-- mandatory for specifying in the interpolator.
data DefaultSwitchesOptions = DefaultSwitchesOptions
  { defSpacesTrimming          :: Maybe Bool
  , defIndentationStripping    :: Maybe Bool
  , defLeadingNewlineStripping :: Maybe Bool
  , defTrailingSpacesStripping :: Maybe Bool
  , defReducedNewlines         :: Maybe Bool
  , defReturnType              :: Maybe ReturnType
  , defMonadic                 :: Maybe Bool
  } deriving stock (Show)

-- | Default 'DefaultSwitchesOptions'.
--
-- This set of switches tries to leave the text as much unmodified as possible.
--
-- It does __not__ define default switches used by @Text.Interpolation.Nyan@
-- module, and you will likely want to enable at least some options here.
basicDefaultSwitchesOptions :: DefaultSwitchesOptions
basicDefaultSwitchesOptions = DefaultSwitchesOptions
  { defSpacesTrimming = Just False
  , defIndentationStripping = Just False
  , defLeadingNewlineStripping = Just False
  , defTrailingSpacesStripping = Just False
  , defReturnType = Just AnyFromBuilder
  , defReducedNewlines = Just False
  , defMonadic = Just False
  }

-- | 'DefaultSwitchesOptions' used in the @Text.Interpolation.Nyan@ module
-- in the default interpolator.
recommendedDefaultSwitchesOptions :: DefaultSwitchesOptions
recommendedDefaultSwitchesOptions = DefaultSwitchesOptions
  { defSpacesTrimming = Just False
  , defIndentationStripping = Just True
  , defLeadingNewlineStripping = Just True
  , defTrailingSpacesStripping = Just True
  , defReturnType = Just AnyFromBuilder
  , defReducedNewlines = Just False
  , defMonadic = Just False
  }

-- | How to expand values in @#{}@ into Haskell AST.
newtype ValueInterpolator = ValueInterpolator
  { runValueInterpolator :: Text -> ExpQ
  }

-- | Transformation that describes how to mark the invisible characters.
--
-- Use 'Monoid' instance to sequence multiple such transformations.
newtype InvisibleCharsPreview = InvisibleCharsPreview
  { replaceInvisibleChars :: String -> String
  } deriving Semigroup via (Endo String)
    deriving Monoid via (Endo String)

-- | Options set when creating an interpolator.
data InterpolatorOptions = InterpolatorOptions
  { defaultSwitchesOptions :: DefaultSwitchesOptions
    -- ^ Default switches options.

  , valueInterpolator      :: ValueInterpolator
    -- ^ Expands text in @#{}@ into AST.
    --
    -- We have to leave this changeable because there is no "perfect" expander.
    -- Using the most appropriate one would require relying on @haskell-src-exts@
    -- package which is quite a heavy-weight dependency.
    -- Some users would prefer a simpler solution which would only allow
    -- variables in @#{}@.
    --
    -- Interpreting the passed text in tricky ways is valid.
    -- For instance, for specialized interpolators @#{var}@
    -- could be expanded to @local'var@, @view varLens@, or more complex
    -- Haskell code.

  , invisibleCharsPreview  :: InvisibleCharsPreview
    -- ^ In case, when the switches are set to preview the resulting text
    -- with invisibles being marked specially (@!!@), how to update the pieces
    -- of text.
  }