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

{-# OPTIONS_GHC -Wno-unused-imports #-}


{- | Tutorial on the nyan-interpolation.

>>> import Nyan.Interpolation
>>>
>>> let who = "world"
>>> in [int||Hello #{who}!|]
"Hello world!"

Variables can be inserted using @#{ ... }@ syntax.

For the meaning of two @||@ after @int@, see the [Switches](#switches) section.

Defaults are:

* Values put via @#{ ... }@ will be rendered with the help of 'Buildable' instance.
* Resulting type will be any type that has 'FromBuilder' instance.
* If multiline text is provided, its indentation, leading newline and trailing spaces are stripped:

>>> :{
  putStrLn [int||
      Yay
      Here goes
        my text
      and ends here.
  |]  -- the position of the enclosing `|]` does not matter
:}
Yay
Here goes
  my text
and ends here.
<BLANKLINE>

Note that the module with interpolator is assumed to be imported with an
implicit import list. This should not spoil your namespace, @int@ is the only
exported definition you may likely get collisions with.

== Values rendering

If necessary, various rendering means can be used for values:

* @#{val}@ uses 'Buildable' instance;
* @#s{val}@ uses 'Show' instance;
* @#l{"text"}@ for text literals;
* @#n{5}@ for decimal numeric literals;

We refer to the text immediately following the @#@ sign as to /rendering mode/.

Rules for rendering modes are not fixed and depend on the imported interpolation module.
If you don't like 'Buildable' and prefer the conventional 'Show' typeclass,
"Text.Interpolation.Nyan.Show" module is your choice.

Also, you can define your own rules for rendering modes as described in [Adding custom rendering modes](#custom-rendering-modes) section.


== Escaping

If you don't want @#{@ to be interpreted as beginning of an interpolated piece, you can escape it:

>>> [int|| My code: \#{ code } |]
" My code: #{ code } "

Newlines can be escaped to avoid their appearance in the rendered text:

>>> :{
  [int||
    Text starts here \
    and continues here
  |]
:}
"Text starts here and continues here"

Note that [@n@ switch](#n-switch) provides another, often a more convenient way
for controlling newlines.

Finally, slash itself is also subject to escaping.

>>> [int|| My code: \\#{ letter } |]
" My code: \s "


#switches#

== Switches

Interpolated text starts from the swithes section.
Switches section consists of a sequence of characters that ends with @|@.

>>> [int|sb| Hey! ]

In most cases, lowercase letter stands for enabling a switch, while uppercase
letter - for disabling it.

Available switches:

==== s (trim [s]paces)

Trims space-like characters around the text.

>>> [int|s| The text  |]
"The text"

Newlines are affected too:

>>> :{
  [int|s|
<BLANKLINE>
    My text
<BLANKLINE>
  |]
:}
"My text"

==== D (no inten[d]ation stripping)

Indentation will not be stripped.

>>> :{
  [int|D|
    List:
      * Item 1
      * Item 2
  |]
:}
"    List:\n      * Item 1\n     * Item 2\n"

Compare with:

>>> :{
  [int||
    List:
      * Item 1
      * Item 2
  |]
:}
"List:\n  * Item 1\n  * Item 2\n"


==== A (no first newline stripping)

By default, the leading newline is stripped, allowing for neater rendering of
multiline interpolators. When the option is disabled via passing @A@ switch,
the initial line feed will appear in the resulting text.

>>> :{
  [int|A|
    The sentence.
  |]
:}
"\nThe sentence.\n"

==== Z (no last line stripping)

Similarly to the previous option, by default he position of the trailing @|]@
does not affect the result.
When the option is disabled via passing @Z@ switch, spaces at the last line
will be preserved.

>>> :{
  [int|Z|
    Foo
  Bar
    |]
:}
"  Foo\nBar\n  "

#n-switch#

==== n (special [n]ewlines)

Each substring of several newlines in a row will be trimmed by one newline.
Adjacent lines will be glued together with space.

This facilitates formatting in case code snippets are large.

>>> :{
  [int|n|
    Value 1 is #{veryLongComputationOfValue1},
    while value 2 is #{
      veryLongComputationOfValue2
    }
<BLANKLINE>
    Here we go.
<BLANKLINE>
<BLANKLINE>
    That's it.
 |]
:}
"Value 1 is 5, while value 2 is 10.\nHere we go.\n\nThat's it."

==== m ([m]onadic)

Accept monadic values.

>>> putStrLn =<< [int|m|Content of foo: #{readFile "foo"}|]

>>> serverConfig & [int|m|Connected to server #{name} at #{host . addr}:#{port . addr}|]

To be stricter, this switch requires only @Applicative@ instance.

In case monadic actions have side effects, they will be applied in the same order
in which braces appear in the quoter. /But you are not going to use this behaviour, don't you?/

==== t (return [t]ext)

The quoter will return concrete t'Text'.

==== T (return lazy [T]ext)

The quoter will return concrete lazy t'LT.Text'.

==== b (return [b]uilder)

The quoter will return concrete t'Builder'.

==== B (return From[B]uildable)

The quoter will return any type with t'FromBuilder' instance.

==== ! (preview)

Quoter will show as an error (non-blocking for the module's build) showing how the
resulting text looks like with all the enabled switches
(but without substitutions).

Passing the second @!@ will additionally highlight invisible characters.

==== ? (help)

Fail and remind what switches are available.

==== Switches interaction

Few rules to know here.

* Spaces trimming is applied last, it never affects whether other switches will
fire or not:

>>> :{
  [int|s|
    Text 1
    Text 2
  |]
:}
"Text 1\nText 2"

* The switch for stripping trailing whitespaces is applied first and
affects indentation stripping to also ignore the line with @|]@:

>>> :{
  [int||
    My text
  |]  -- this is safe, minimal detected indentation is still 2
:}

* Newlines reduction (@n@) is applied additionally to leading newline stripping (@a@):

>>> :{
  [int|n|
<BLANKLINE>
<BLANKLINE>
  Value 1 is #{veryLongComputationOfValue1},
  value 2 is #{veryLongComputationOfValue2}
<BLANKLINE>
 |]
:}
"\nValue 1 is 5, value 2 is 10\n"


=== Customizing the interpolator

There are two main vectors of customization:

* Changing the quasi-quoter, this includes default switches set, how values are interpolated, e.t.c.
* Editing available rendering modes.

The source of "Text.Interpolation.Nyan" can be a good example of grouping those
to provide a ready-for-use interpolator.

==== Changing default switches

You can use 'mkInt' to supply the desired set of default switches and thus create your own interpolator for the project-wide use.

@
import Text.Interpolation.Nyan.Core

int :: QuasiQuoter
int = mkInt defaultInterpolationOptions
  { switchesOptions = basicDefaultSwitchesOptions
    { spacesTrimming = True
    , returnType = ConcreteText
    }
  }

-- In another module --
-- Spaces trimmed, no indentation stripped, returns @Text@
[int|| My text |]

-- Spaces not trimmed, indentation stripped, returns @FromBuilder a => a@
[int|SdB| My text |]
@


#custom-rendering-modes#

==== Adding custom rendering modes

Rendering mode @xxx@ refers to whatever @rmode'xxx@ value that is available in scope.

For instance, the rendering mode for 'Show' is declared as

@
rmode's :: Show a => RMode a
rmode's = RMode $ build . show
@

Importing this @rmode's@ value will make @s@ mode with 'Show' semantics usable.

Declaring a rendering mode locally will also work:

>>> rmode'yay :: RMode Builder
>>> rmode'yay = RMode \t -> "*" <> t <> "!*"
>>>
>>> [int|t|Say #yay{"hello"}|]
"Say *hello!*"

You can declare /modesets/ ⁠— modules that export several rendering modes.
The user can then easily pick all or some of the modes by importing that module.

-}
module Text.Interpolation.Nyan.Tutorial where

import Data.Text (Text)
import qualified Data.Text.Lazy as LT
import Fmt (Buildable, Builder)
import Fmt.Internal.Core (FromBuilder)

import Text.Interpolation.Nyan.Core (mkInt)