{-# LANGUAGE FlexibleContexts #-}

{- |
Copyright:  (c) 2019 Kowainik
License:    MPL-2.0
Maintainer: Kowainik <xrom.xkov@gmail.com>

This package allows to use [shortcut-links](https://hackage.haskell.org/package/shortcut-links)
package in websites generated by [hakyll](https://hackage.haskell.org/package/hakyll).

The flexible interface allows to use the supported huge collection of shortcuts
along with using custom ones.
-}

module Hakyll.ShortcutLinks
       ( -- * Pandoc functions
         -- $pandoc
         applyShortcuts
       , applyAllShortcuts

         -- * Hakyll functions
         -- $hakyll
       , shortcutLinksCompiler
       , allShortcutLinksCompiler

         -- * Shortcut-links reexports
         -- $sh
       , module Sh
         -- $allSh
       , module ShortcutLinks.All
       ) where

import Control.Monad.Except (MonadError (..))
import Data.Text (Text)
import Hakyll (Compiler, Item, defaultHakyllReaderOptions, defaultHakyllWriterOptions,
               pandocCompilerWithTransformM)
import ShortcutLinks (Result (..), Shortcut, allShortcuts, useShortcutFrom)
import Text.Pandoc.Generic (bottomUpM)

import Hakyll.ShortcutLinks.Parser (parseShortcut)

-- exports
import ShortcutLinks as Sh
import ShortcutLinks.All

import qualified Data.Text as T
import qualified Text.Pandoc.Definition as Pandoc


{- $pandoc
Functions to transform 'Pandoc.Pandoc' documents. These functions modify
markdown links to the extended links.

These are the most generic functions. They work inside the monad @m@ that has
@'MonadError' ['String']@ instance.
You can use the pure version of these function because there's 'MonadError'
instance for 'Either':

@
applyShorcuts :: [(['Text'], 'Shortcut')] -> 'Pandoc.Pandoc' -> 'Either' ['String'] 'Pandoc.Pandoc'
applyAllShorcuts :: 'Pandoc.Pandoc' -> 'Either' ['String'] 'Pandoc.Pandoc'
@

If you have your own @hakyll@ options for your custom pandoc compiler, you can
use this function like this:

@
'pandocCompilerWithTransformM'
    myHakyllReaderOptions
    myHakyllWriterOptions
    ('applyShortcuts' myShortcuts)
@


-}

{- | Modifies markdown shortcut links to the extended version and returns
'Pandoc.Pandoc' with the complete links instead.

Unlike 'applyAllShortcuts' which uses the hardcoded list of the possible
shortcuts (see 'allShortcuts'), the 'applyShortcuts' function uses the given
list of custom provided shortcuts.
For your help you can use 'ShortcutLinks.All' module to see all available
shortcuts.

If you want to add a couple of custom shortcuts to the list of already existing
shortcuts you can do it in the following way:

@
(["hk", "hackage"], 'hackage') : 'allShortcuts'
@
-}
applyShortcuts
    :: forall m . MonadError [String] m
    => [([Text], Shortcut)]  -- ^ Shortcuts
    -> Pandoc.Pandoc         -- ^ Pandoc document that possibly contains shortened links
    -> m Pandoc.Pandoc       -- ^ Result pandoc document with shorcuts expanded
applyShortcuts shortcuts = bottomUpM applyLink
  where
    applyLink :: Pandoc.Inline -> m Pandoc.Inline
    applyLink l@(Pandoc.Link attr inl (url, title)) = case parseShortcut $ T.pack url of
        Right (name, option, text) -> maybe (checkTitle inl) pure text >>= \txtTitle ->
            case useShortcutFrom shortcuts name option txtTitle of
                Success link -> pure $ Pandoc.Link attr inl (T.unpack link, title)
                Warning ws _ -> throwError ws
                Failure msg  -> throwError [msg]
        Left _ -> pure l  -- the link is not shortcut
    applyLink other = pure other

    checkTitle :: [Pandoc.Inline] -> m Text
    checkTitle = \case
        [] -> throwError ["Empty shortcut link title arguments"]
        [Pandoc.Str s] -> pure $ T.pack s
        _ -> throwError ["Shortcut title is not a single string element"]

{- |  Modifies markdown shortcut links to the extended version and returns
'Pandoc.Pandoc' with the complete links instead.

Similar to 'applyShortcuts' but uses 'allShortcuts' as a list of shortcuts to
parse against.
-}
applyAllShortcuts :: MonadError [String] m => Pandoc.Pandoc -> m Pandoc.Pandoc
applyAllShortcuts = applyShortcuts allShortcuts

{- $hakyll
Functions to integrate shortcut links to [hakyll](http://hackage.haskell.org/package/hakyll).

@hakyll-shortcut-links@ provides out-of-the-box 'Compiler's that translate
markdown documents with shortcut links into the documents with extended links.

Usually you would want to use this feature on your blog post markdown files.
Assuming that you already have similar code for it:

@
match "blog/*" $ do
    route $ setExtension "html"
    compile $
        __pandocCompiler__
            >>= loadAndApplyTemplate "templates/post.html" defaultContext
            >>= relativizeUrls
@

All that you would need to do is to replace 'Hakyll.pandocCompiler' with
'shortcutLinksCompiler' or 'allShortcutLinksCompiler':

@
match "blog/*" $ do
    route $ setExtension "html"
    compile $
        __'allShortcutLinksCompiler'__
            >>= loadAndApplyTemplate "templates/post.html" defaultContext
            >>= relativizeUrls
@

-}

{- | Our own pandoc compiler which parses shortcut links automatically.
It takes a custom list of shortcut links to be used in the document.
-}
shortcutLinksCompiler :: [([Text], Shortcut)] -> Compiler (Item String)
shortcutLinksCompiler = pandocCompilerWithTransformM
    defaultHakyllReaderOptions
    defaultHakyllWriterOptions
    . applyShortcuts

{- | Our own pandoc compiler which parses shortcut links automatically. Same as
'shortcutLinksCompiler' but passes 'allShortcuts' as an argument.
-}
allShortcutLinksCompiler :: Compiler (Item String)
allShortcutLinksCompiler = shortcutLinksCompiler allShortcuts

{- $sh
This is the module from @shortcut-links@ library that introduces the functions
that by given shortcuts creates the 'Result'ing URL (if possible).
-}

{- $allSh
This module stores a large number of supported 'Shortcut's.
It also reexports a useful function 'allShortcuts' that is a list of all
shortcuts, together with suggested names for them.

In @hakyll-shortcut-links@ we are exporting both functions that work with the
standard list of 'allShortcuts', but also we provide the option to use your own
lists of shortcuts (including self-created ones).

For example, if you want to use just 'github' and 'hackage' shortcuts you can
create the following list:

@
(["github"], github) : (["hackage"], hackage) : []
@

If you want to create your own shortcut that is not included in
"ShortcutLinks.All" module you can achieve that implementing the following
function:

@
kowainik :: 'Shortcut'
kowainik _ text = pure $ "https://kowainik.github.io/posts/" <> text

myShortcuts :: [(['Text'], 'Shortcut')]
myShortcuts = [(["kowainik"], kowainik)]
@

And it would work like this:

@
[blog post]\(@kowainik:2019-02-06-style-guide)

=>

[blog post]\(https:\/\/kowainik.github.io\/posts\/2019-02-06-style-guide)
@
-}