-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ

{-# LANGUAGE TypeFamilyDependencies, UndecidableSuperClasses #-}
{-# OPTIONS_GHC -Wno-redundant-constraints #-}

-- | Renderable documentation injected to contract code.
module Michelson.Doc
  ( DocItem (..)
  , docItemPosition
  , DocItemId (..)
  , DocItemPlacementKind (..)
  , DocItemPos (..)
  , DocItemRef (..)
  , DocItemReferencedKind
  , DocSectionNameStyle (..)
  , SomeDocItem (..)
  , SomeDocDefinitionItem (..)
  , DocElem (..)
  , DocSection (..)
  , DocBlock
  , SubDoc (..)
  , ContractDoc (..)
  , DocGrouping
  , cdContentsL
  , cdDefinitionsL
  , cdDefinitionsSetL
  , cdDefinitionIdsL
  , deIsAtomic
  , subDocToMarkdown
  , docItemToBlock
  , docItemSectionRef
  , lookupDocBlockSection
  , contractDocToMarkdown
  , contractDocToToc
  , docGroupContent
  , docDefinitionRef
  , mdTocFromRef
  , WithFinalizedDoc (..)
  , finalizedAsIs
  , ContainsDoc (..)
  , ContainsUpdateableDoc (..)
  , buildDoc
  , buildMarkdownDoc
  , modifyDoc

  , DGeneralInfoSection (..)
  , DName (..)
  , DDescription (..)
  , DGitRevision (..)
  , GitRepoSettings (..)
  , mkDGitRevision
  , morleyRepoSettings
  , DComment (..)
  , DAnchor (..)
  , DToc (..)
  , DConversionInfo (..)
  , attachGitInfo
  , attachToc
  , attachDocCommons
  ) where

import qualified Data.Map as M
import qualified Data.Map.Merge.Strict as M
import qualified Data.Set as S
import qualified Data.Text as T
import qualified Data.Text.Lazy as LT
import Data.Typeable (cast, typeRep)
import Development.GitRev (gitCommitDate, gitHash)
import Fmt (Buildable, build, fmt, (+|), (+||), (|+), (||+))
import qualified Language.Haskell.TH as TH
import qualified Language.Haskell.TH.Lift as TH
import System.Environment (lookupEnv)
import qualified Text.Show

import Util.Instances ()
import Util.Lens
import Util.Markdown
import Util.Type
import Util.Typeable

-- | A piece of documentation describing one property of a thing,
-- be it a name or description of a contract, or an error throwable
-- by given endpoint.
--
-- Items of the same type appear close to each other in a rendered documentation
-- and form a /section/.
--
-- Doc items are later injected into a contract code via a dedicated nop-like
-- instruction. Normally doc items which belong to one section appear in
-- resulting doc in the same order in which they appeared in the contract.
--
-- While documentation framework grows, this typeclass acquires more and more
-- methods for fine tuning of existing rendering logic because we don't want
-- to break backward compatibility, hope one day we will make everything
-- concise :(
-- E.g. all rendering and reording stuff could be merged in one method, and
-- we could have several template implementations for it which would allow
-- user to specify only stuff relevant to his case.
class (Typeable d, DOrd d) => DocItem d where
  -- | Position of this item in the resulting documentation;
  -- the smaller the value, the higher the section with this element
  -- will be placed. If the position is the same as other doc items,
  -- they will be placed base on their name, alphabetically.
  --
  -- Documentation structure is not necessarily flat.
  -- If some doc item consolidates a whole documentation block within it,
  -- this block will have its own placement of items independent from outer parts
  -- of the doc.
  docItemPos :: Natural

  -- | When multiple items of the same type belong to one section, how
  -- this section will be called.
  --
  -- If not provided, section will contain just untitled content.
  docItemSectionName :: Maybe Text

  -- | Description of a section.
  --
  -- Can be used to mention some common things about all elements of this section.
  -- Markdown syntax is permitted here.
  docItemSectionDescription :: Maybe Markdown
  docItemSectionDescription = Maybe Markdown
forall a. Maybe a
Nothing

  -- | How to render section name.
  --
  -- Takes effect only if section name is set.
  docItemSectionNameStyle :: DocSectionNameStyle
  docItemSectionNameStyle = DocSectionNameStyle
DocSectionNameBig

  -- | Defines where given doc item should be put. There are two options:
  -- 1. Inline right here (default behaviour);
  -- 2. Put into definitions section.
  --
  -- Note that we require all doc items with "in definitions" placement to
  -- have 'Eq' and 'Ord' instances which comply the following law:
  -- if two documentation items describe the same entity or property, they
  -- should be considered equal.
  type DocItemPlacement d :: DocItemPlacementKind
  type DocItemPlacement _ = 'DocItemInlined

  type DocItemReferenced d :: DocItemReferencedKind
  type DocItemReferenced _ = 'False

  -- | Defines a function which constructs an unique identifier of given doc item,
  -- if it has been decided to put the doc item into definitions section.
  --
  -- Identifier should be unique both among doc items of the same type and items
  -- of other types. Thus, consider using "typeId-contentId" pattern.
  docItemRef :: d -> DocItemRef (DocItemPlacement d) (DocItemReferenced d)
  default docItemRef
    :: ( DocItemPlacement d ~ 'DocItemInlined
       , DocItemReferenced d ~ 'False
       )
    => d -> DocItemRef (DocItemPlacement d) (DocItemReferenced d)
  docItemRef d
_ = DocItemRef 'DocItemInlined 'False
DocItemRef (DocItemPlacement d) (DocItemReferenced d)
DocItemNoRef

  -- | Render given doc item to Markdown, preferably one line,
  -- optionally with header.
  --
  -- Accepts the smallest allowed level of header.
  -- (Using smaller value than provided one will interfere with existing
  -- headers thus delivering mess).
  docItemToMarkdown :: HeaderLevel -> d -> Markdown

  -- | Render table of contents entry for given doc item to Markdown.
  docItemToToc :: HeaderLevel -> d -> Markdown
  docItemToToc HeaderLevel
_ d
_ = Markdown
""

  -- | All doc items which this doc item refers to.
  --
  -- They will automatically be put to definitions as soon as given doc item
  -- is detected.
  docItemDependencies :: d -> [SomeDocDefinitionItem]
  docItemDependencies d
_ = []

  -- | This function accepts doc items put under the same section in the order
  -- in which they appeared in the contract and returns their new desired order.
  -- It's also fine to use this function for filtering or merging doc items.
  --
  -- Default implementation
  -- * leaves inlined items as is;
  -- * for items put to definitions, lexicographically sorts them by their id.
  docItemsOrder :: [d] -> [d]
  docItemsOrder = \case
    [] -> []
    docItems :: [d]
docItems@(d
someDocItem : [d]
_) -> case d -> DocItemRef (DocItemPlacement d) (DocItemReferenced d)
forall d.
DocItem d =>
d -> DocItemRef (DocItemPlacement d) (DocItemReferenced d)
docItemRef d
someDocItem of
      DocItemRef (DocItemPlacement d) (DocItemReferenced d)
DocItemNoRef -> [d]
docItems
      DocItemRef DocItemId
_ ->
        [d] -> [d]
forall d.
(DocItem d, DocItemPlacement d ~ 'DocItemInDefinitions) =>
[d] -> [d]
docItemsOrderById [d]
docItems
      DocItemRefInlined DocItemId
_ ->
        [d]
docItems

-- | Generate 'DToc' entry anchor from 'docItemRef'.
mdTocFromRef :: (DocItem d, DocItemReferenced d ~ 'True) => HeaderLevel -> Markdown -> d -> Markdown
mdTocFromRef :: HeaderLevel -> Markdown -> d -> Markdown
mdTocFromRef HeaderLevel
lvl Markdown
text d
d =
  HeaderLevel -> Markdown -> Anchor -> Markdown
forall anchor.
ToAnchor anchor =>
HeaderLevel -> Markdown -> anchor -> Markdown
mdToc HeaderLevel
lvl Markdown
text (DocItemRef (DocItemPlacement d) 'True -> Anchor
forall anchor. ToAnchor anchor => anchor -> Anchor
toAnchor (DocItemRef (DocItemPlacement d) 'True -> Anchor)
-> DocItemRef (DocItemPlacement d) 'True -> Anchor
forall a b. (a -> b) -> a -> b
$ d -> DocItemRef (DocItemPlacement d) (DocItemReferenced d)
forall d.
DocItem d =>
d -> DocItemRef (DocItemPlacement d) (DocItemReferenced d)
docItemRef d
d)

-- | Get doc item position at term-level.
docItemPosition :: forall d. DocItem d => DocItemPos
docItemPosition :: DocItemPos
docItemPosition = (Natural, Text) -> DocItemPos
DocItemPos (DocItem d => Natural
forall d. DocItem d => Natural
docItemPos @d, TypeRep -> Text
forall b a. (Show a, IsString b) => a -> b
show (Proxy d -> TypeRep
forall k (proxy :: k -> *) (a :: k).
Typeable a =>
proxy a -> TypeRep
typeRep (Proxy d -> TypeRep) -> Proxy d -> TypeRep
forall a b. (a -> b) -> a -> b
$ Proxy d
forall k (t :: k). Proxy t
Proxy @d))

-- | Render an item into Markdown block with all required adjustments.
docItemToMarkdownFull :: DocItem d => HeaderLevel -> d -> Markdown
docItemToMarkdownFull :: HeaderLevel -> d -> Markdown
docItemToMarkdownFull HeaderLevel
l d
d =
  Markdown
manchor Markdown -> Markdown -> Markdown
forall a. Semigroup a => a -> a -> a
<> HeaderLevel -> d -> Markdown
forall d. DocItem d => HeaderLevel -> d -> Markdown
docItemToMarkdown HeaderLevel
l d
d Markdown -> Markdown -> Markdown
forall a. Semigroup a => a -> a -> a
<> Markdown
"\n\n"
  where
    manchor :: Markdown
manchor = case d -> DocItemRef (DocItemPlacement d) (DocItemReferenced d)
forall d.
DocItem d =>
d -> DocItemRef (DocItemPlacement d) (DocItemReferenced d)
docItemRef d
d of
      DocItemRef DocItemId
docItemId -> DocItemId -> Markdown
forall anchor. ToAnchor anchor => anchor -> Markdown
mdAnchor DocItemId
docItemId
      DocItemRefInlined DocItemId
docItemId -> DocItemId -> Markdown
forall anchor. ToAnchor anchor => anchor -> Markdown
mdAnchor DocItemId
docItemId
      DocItemRef (DocItemPlacement d) (DocItemReferenced d)
DocItemNoRef -> Markdown
""

-- | Order items by their 'docItemId'.
docItemsOrderById
  :: forall d. (DocItem d, DocItemPlacement d ~ 'DocItemInDefinitions)
  => [d] -> [d]
docItemsOrderById :: [d] -> [d]
docItemsOrderById [d]
docItems =
  let getDocItemId :: d -> DocItemId
      getDocItemId :: d -> DocItemId
getDocItemId d
d = case d -> DocItemRef (DocItemPlacement d) (DocItemReferenced d)
forall d.
DocItem d =>
d -> DocItemRef (DocItemPlacement d) (DocItemReferenced d)
docItemRef d
d of { DocItemRef DocItemId
di -> DocItemId
di }
  in (d -> DocItemId) -> [d] -> [d]
forall b a. Ord b => (a -> b) -> [a] -> [a]
sortOn d -> DocItemId
getDocItemId [d]
docItems

-- | Make a reference to doc item in definitions.
docDefinitionRef
  :: (DocItem d, DocItemPlacement d ~ 'DocItemInDefinitions)
  => Markdown -> d -> Markdown
docDefinitionRef :: Markdown -> d -> Markdown
docDefinitionRef Markdown
refText d
d = case d -> DocItemRef (DocItemPlacement d) (DocItemReferenced d)
forall d.
DocItem d =>
d -> DocItemRef (DocItemPlacement d) (DocItemReferenced d)
docItemRef d
d of
  DocItemRef DocItemId
docItemId -> Markdown -> DocItemId -> Markdown
forall anchor. ToAnchor anchor => Markdown -> anchor -> Markdown
mdLocalRef Markdown
refText DocItemId
docItemId

-- | Some unique identifier of a doc item.
--
-- All doc items which should be refer-able need to have this identifier.
newtype DocItemId = DocItemId Text
  deriving stock (DocItemId -> DocItemId -> Bool
(DocItemId -> DocItemId -> Bool)
-> (DocItemId -> DocItemId -> Bool) -> Eq DocItemId
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: DocItemId -> DocItemId -> Bool
$c/= :: DocItemId -> DocItemId -> Bool
== :: DocItemId -> DocItemId -> Bool
$c== :: DocItemId -> DocItemId -> Bool
Eq, Eq DocItemId
Eq DocItemId
-> (DocItemId -> DocItemId -> Ordering)
-> (DocItemId -> DocItemId -> Bool)
-> (DocItemId -> DocItemId -> Bool)
-> (DocItemId -> DocItemId -> Bool)
-> (DocItemId -> DocItemId -> Bool)
-> (DocItemId -> DocItemId -> DocItemId)
-> (DocItemId -> DocItemId -> DocItemId)
-> Ord DocItemId
DocItemId -> DocItemId -> Bool
DocItemId -> DocItemId -> Ordering
DocItemId -> DocItemId -> DocItemId
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: DocItemId -> DocItemId -> DocItemId
$cmin :: DocItemId -> DocItemId -> DocItemId
max :: DocItemId -> DocItemId -> DocItemId
$cmax :: DocItemId -> DocItemId -> DocItemId
>= :: DocItemId -> DocItemId -> Bool
$c>= :: DocItemId -> DocItemId -> Bool
> :: DocItemId -> DocItemId -> Bool
$c> :: DocItemId -> DocItemId -> Bool
<= :: DocItemId -> DocItemId -> Bool
$c<= :: DocItemId -> DocItemId -> Bool
< :: DocItemId -> DocItemId -> Bool
$c< :: DocItemId -> DocItemId -> Bool
compare :: DocItemId -> DocItemId -> Ordering
$ccompare :: DocItemId -> DocItemId -> Ordering
$cp1Ord :: Eq DocItemId
Ord, Int -> DocItemId -> ShowS
[DocItemId] -> ShowS
DocItemId -> String
(Int -> DocItemId -> ShowS)
-> (DocItemId -> String)
-> ([DocItemId] -> ShowS)
-> Show DocItemId
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [DocItemId] -> ShowS
$cshowList :: [DocItemId] -> ShowS
show :: DocItemId -> String
$cshow :: DocItemId -> String
showsPrec :: Int -> DocItemId -> ShowS
$cshowsPrec :: Int -> DocItemId -> ShowS
Show)
  deriving newtype (DocItemId -> Anchor
(DocItemId -> Anchor) -> ToAnchor DocItemId
forall anchor. (anchor -> Anchor) -> ToAnchor anchor
toAnchor :: DocItemId -> Anchor
$ctoAnchor :: DocItemId -> Anchor
ToAnchor)

-- | Position of all doc items of some type.
newtype DocItemPos = DocItemPos (Natural, Text)
  deriving stock (DocItemPos -> DocItemPos -> Bool
(DocItemPos -> DocItemPos -> Bool)
-> (DocItemPos -> DocItemPos -> Bool) -> Eq DocItemPos
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: DocItemPos -> DocItemPos -> Bool
$c/= :: DocItemPos -> DocItemPos -> Bool
== :: DocItemPos -> DocItemPos -> Bool
$c== :: DocItemPos -> DocItemPos -> Bool
Eq, Eq DocItemPos
Eq DocItemPos
-> (DocItemPos -> DocItemPos -> Ordering)
-> (DocItemPos -> DocItemPos -> Bool)
-> (DocItemPos -> DocItemPos -> Bool)
-> (DocItemPos -> DocItemPos -> Bool)
-> (DocItemPos -> DocItemPos -> Bool)
-> (DocItemPos -> DocItemPos -> DocItemPos)
-> (DocItemPos -> DocItemPos -> DocItemPos)
-> Ord DocItemPos
DocItemPos -> DocItemPos -> Bool
DocItemPos -> DocItemPos -> Ordering
DocItemPos -> DocItemPos -> DocItemPos
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: DocItemPos -> DocItemPos -> DocItemPos
$cmin :: DocItemPos -> DocItemPos -> DocItemPos
max :: DocItemPos -> DocItemPos -> DocItemPos
$cmax :: DocItemPos -> DocItemPos -> DocItemPos
>= :: DocItemPos -> DocItemPos -> Bool
$c>= :: DocItemPos -> DocItemPos -> Bool
> :: DocItemPos -> DocItemPos -> Bool
$c> :: DocItemPos -> DocItemPos -> Bool
<= :: DocItemPos -> DocItemPos -> Bool
$c<= :: DocItemPos -> DocItemPos -> Bool
< :: DocItemPos -> DocItemPos -> Bool
$c< :: DocItemPos -> DocItemPos -> Bool
compare :: DocItemPos -> DocItemPos -> Ordering
$ccompare :: DocItemPos -> DocItemPos -> Ordering
$cp1Ord :: Eq DocItemPos
Ord, Int -> DocItemPos -> ShowS
[DocItemPos] -> ShowS
DocItemPos -> String
(Int -> DocItemPos -> ShowS)
-> (DocItemPos -> String)
-> ([DocItemPos] -> ShowS)
-> Show DocItemPos
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [DocItemPos] -> ShowS
$cshowList :: [DocItemPos] -> ShowS
show :: DocItemPos -> String
$cshow :: DocItemPos -> String
showsPrec :: Int -> DocItemPos -> ShowS
$cshowsPrec :: Int -> DocItemPos -> ShowS
Show)

instance Buildable DocItemPos where
  build :: DocItemPos -> Markdown
build (DocItemPos (Natural
a, Text
_)) = Natural -> Markdown
forall p. Buildable p => p -> Markdown
build Natural
a

-- | Where do we place given doc item.
data DocItemPlacementKind
  = DocItemInlined
    -- ^ Placed in the document content itself.
  | DocItemInDefinitions
    -- ^ Placed in dedicated definitions section; can later be referenced.

-- | Type-level check whether or not a doc item can be referenced.
type DocItemReferencedKind = Bool

data DocItemRef (p :: DocItemPlacementKind) (r :: DocItemReferencedKind) where
  DocItemRef        :: DocItemId -> DocItemRef 'DocItemInDefinitions 'True
  DocItemRefInlined :: DocItemId -> DocItemRef 'DocItemInlined 'True
  DocItemNoRef      :: DocItemRef 'DocItemInlined 'False

instance ToAnchor (DocItemRef d 'True) where
  toAnchor :: DocItemRef d 'True -> Anchor
toAnchor (DocItemRef DocItemId
ref) = DocItemId -> Anchor
forall anchor. ToAnchor anchor => anchor -> Anchor
toAnchor DocItemId
ref
  toAnchor (DocItemRefInlined DocItemId
ref) = DocItemId -> Anchor
forall anchor. ToAnchor anchor => anchor -> Anchor
toAnchor DocItemId
ref

-- | How to render section name.
data DocSectionNameStyle
  = DocSectionNameBig
    -- ^ Suitable for block name.
  | DocSectionNameSmall
    -- ^ Suitable for subsection title within block.

-- | Hides some documentation item.
data SomeDocItem where
  SomeDocItem :: DocItem d => d -> SomeDocItem

-- NFData instance is needed for benchmarks and we want to avoid requiring users
-- to implement NFData instance for every single DocItem and they should not
-- affect the performance anyway.
instance NFData SomeDocItem where
  rnf :: SomeDocItem -> ()
rnf (SomeDocItem d
_) = ()

-- | Hides some documentation item which is put to "definitions" section.
data SomeDocDefinitionItem where
  SomeDocDefinitionItem
    :: (DocItem d, DocItemPlacement d ~ 'DocItemInDefinitions)
    => d -> SomeDocDefinitionItem

instance Eq SomeDocDefinitionItem where
  SomeDocDefinitionItem d
d1 == :: SomeDocDefinitionItem -> SomeDocDefinitionItem -> Bool
== SomeDocDefinitionItem d
d2 =
    d
d1 d -> d -> Bool
forall a1 a2. (Typeable a1, Typeable a2, Eq a1) => a1 -> a2 -> Bool
`eqExt` d
d2
instance Ord SomeDocDefinitionItem where
  SomeDocDefinitionItem d
d1 compare :: SomeDocDefinitionItem -> SomeDocDefinitionItem -> Ordering
`compare` SomeDocDefinitionItem d
d2 =
    d
d1 d -> d -> Ordering
forall a1 a2.
(Typeable a1, Typeable a2, Ord a1) =>
a1 -> a2 -> Ordering
`compareExt` d
d2

-- | To automatically derive @instance Show Michelson.Typed.Instr@ later.
instance Show SomeDocItem where
  show :: SomeDocItem -> String
show SomeDocItem
_ = String
"<doc item>"

type family DOrd d :: Constraint where
  DOrd d = If (DocItemPlacement d == 'DocItemInDefinitions)
             (Ord d) (() :: Constraint)

-- | A doc item which we store, along with related information.
data DocElem d = DocElem
  { DocElem d -> d
deItem :: d
    -- ^ Doc item itself.
  , DocElem d -> Maybe SubDoc
deSub :: Maybe SubDoc
    -- ^ Subdocumentation, if given item is a group.
  }

-- | Whether given 'DocElem' is atomic.
--
-- Normally, atomic 'DocElem's are ones appearing in @DOC_ITEM@ instruction,
-- and non-atomic ones are put to @DocGroup@.
deIsAtomic :: DocElem d -> Bool
deIsAtomic :: DocElem d -> Bool
deIsAtomic = Maybe SubDoc -> Bool
forall a. Maybe a -> Bool
isNothing (Maybe SubDoc -> Bool)
-> (DocElem d -> Maybe SubDoc) -> DocElem d -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DocElem d -> Maybe SubDoc
forall d. DocElem d -> Maybe SubDoc
deSub

-- | Several doc items of the same type.
data DocSection = forall d. DocItem d => DocSection (NonEmpty $ DocElem d)

instance Show DocSection where
  show :: DocSection -> String
show (DocSection (NonEmpty (DocElem d)
ds :: NonEmpty (DocElem d))) =
    String
"Doc items section: " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> TypeRep -> String
forall b a. (Show a, IsString b) => a -> b
show (Proxy d -> TypeRep
forall k (proxy :: k -> *) (a :: k).
Typeable a =>
proxy a -> TypeRep
typeRep (Proxy d -> TypeRep) -> Proxy d -> TypeRep
forall a b. (a -> b) -> a -> b
$ Proxy d
forall k (t :: k). Proxy t
Proxy @d) String -> ShowS
forall a. Semigroup a => a -> a -> a
<>
    String
" / " String -> ShowS
forall a. Semigroup a => a -> a -> a
<> Int -> String
forall b a. (Show a, IsString b) => a -> b
show (NonEmpty (DocElem d) -> Int
forall t. Container t => t -> Int
length NonEmpty (DocElem d)
ds) String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
" item(s)"

unsafeAppendDocSection
  :: HasCallStack
  => DocSection -> DocSection -> DocSection
unsafeAppendDocSection :: DocSection -> DocSection -> DocSection
unsafeAppendDocSection (DocSection NonEmpty $ DocElem d
ls) (DocSection NonEmpty $ DocElem d
rs) =
  (NonEmpty $ DocElem d) -> DocSection
forall d. DocItem d => (NonEmpty $ DocElem d) -> DocSection
DocSection ((NonEmpty $ DocElem d) -> DocSection)
-> (NonEmpty $ DocElem d) -> DocSection
forall a b. (a -> b) -> a -> b
$ (NonEmpty $ DocElem d) -> [DocElem d] -> NonEmpty $ DocElem d
forall d1 d2.
(Typeable d1, Typeable d2, HasCallStack) =>
NonEmpty d1 -> [d2] -> NonEmpty d1
unsafeAppendDocSectionImpl NonEmpty $ DocElem d
ls ((NonEmpty $ DocElem d) -> [Element (NonEmpty $ DocElem d)]
forall t. Container t => t -> [Element t]
toList NonEmpty $ DocElem d
rs)

unsafeAppendDocSectionImpl
  :: forall d1 d2.
     (Typeable d1, Typeable d2, HasCallStack)
  => NonEmpty d1 -> [d2] -> NonEmpty d1
unsafeAppendDocSectionImpl :: NonEmpty d1 -> [d2] -> NonEmpty d1
unsafeAppendDocSectionImpl (d1
l :| [d1]
ls) [d2]
rs =
  let rs' :: [d1]
rs' = [d2]
rs [d2] -> (d2 -> d1) -> [d1]
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \d2
r -> d2 -> Maybe d1
forall a b. (Typeable a, Typeable b) => a -> Maybe b
cast d2
r Maybe d1 -> d1 -> d1
forall a. Maybe a -> a -> a
?: d1
onTypeMismatch
  in d1
l d1 -> [d1] -> NonEmpty d1
forall a. a -> [a] -> NonEmpty a
:| [d1]
ls [d1] -> [d1] -> [d1]
forall a. Semigroup a => a -> a -> a
<> [d1]
rs'
  where
    onTypeMismatch :: d1
onTypeMismatch =
      Text -> d1
forall a. HasCallStack => Text -> a
error (Text -> d1) -> Text -> d1
forall a b. (a -> b) -> a -> b
$ Markdown
"appending doc sections for doc items of different types:"
              Markdown -> Markdown -> Text
forall b. FromBuilder b => Markdown -> Markdown -> b
+|| Proxy d1 -> TypeRep
forall k (proxy :: k -> *) (a :: k).
Typeable a =>
proxy a -> TypeRep
typeRep (Proxy d1
forall k (t :: k). Proxy t
Proxy @d1) TypeRep -> Markdown -> Markdown
forall a b. (Show a, FromBuilder b) => a -> Markdown -> b
||+ Markdown
" and " Markdown -> Markdown -> Markdown
forall b. FromBuilder b => Markdown -> Markdown -> b
+|| Proxy d2 -> TypeRep
forall k (proxy :: k -> *) (a :: k).
Typeable a =>
proxy a -> TypeRep
typeRep (Proxy d2
forall k (t :: k). Proxy t
Proxy @d2) TypeRep -> Markdown -> Markdown
forall a b. (Show a, FromBuilder b) => a -> Markdown -> b
||+ Markdown
""

-- | A map from positions to document elements.
--
-- This form effeciently keeps documentation for its incremental building.
-- Doc items here appear close to how they were located in the contract;
-- for instance, 'docItemsOrder' is not yet applied at this stage.
-- You only can be sure that items within each group are splitted across
-- sections correctly.
type DocBlock = Map DocItemPos DocSection

-- | Render a documentation block.
docBlockToMarkdown :: HeaderLevel -> DocBlock -> Markdown
docBlockToMarkdown :: HeaderLevel -> DocBlock -> Markdown
docBlockToMarkdown HeaderLevel
hl DocBlock
block =
  [Markdown] -> Markdown
forall a. Monoid a => [a] -> a
mconcat ([Markdown] -> Markdown) -> [Markdown] -> Markdown
forall a b. (a -> b) -> a -> b
$ DocBlock -> [DocSection]
forall k a. Map k a -> [a]
M.elems DocBlock
block [DocSection] -> (DocSection -> Markdown) -> [Markdown]
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \(DocSection items :: NonEmpty $ DocElem d
items@((DocElem d
_ :: DocElem di) :| [DocElem d]
_)) ->
    let sectionName :: Maybe Text
sectionName = DocItem d => Maybe Text
forall d. DocItem d => Maybe Text
docItemSectionName @di
        sectionNameStyle :: DocSectionNameStyle
sectionNameStyle = DocItem d => DocSectionNameStyle
forall d. DocItem d => DocSectionNameStyle
docItemSectionNameStyle @di
        (Markdown
sectionNameFull, HeaderLevel -> HeaderLevel
headerLevelDelta) =
          case Maybe Text
sectionName of
            Maybe Text
Nothing -> (Markdown
"", HeaderLevel -> HeaderLevel
forall a. a -> a
id)
            Just Text
sn ->
              let sn' :: Markdown
sn' = Text -> Markdown
forall p. Buildable p => p -> Markdown
build Text
sn
              in case DocSectionNameStyle
sectionNameStyle of
                  DocSectionNameStyle
DocSectionNameBig ->
                    (HeaderLevel -> Markdown -> Markdown
mdHeader HeaderLevel
hl Markdown
sn', HeaderLevel -> HeaderLevel
nextHeaderLevel)
                  DocSectionNameStyle
DocSectionNameSmall ->
                    ( Markdown -> Markdown
mdSubsectionTitle Markdown
sn' Markdown -> Markdown -> Markdown
forall a. Semigroup a => a -> a -> a
<> Markdown
"\n"
                    , Text -> HeaderLevel -> HeaderLevel
forall a. HasCallStack => Text -> a
error (Text -> HeaderLevel -> HeaderLevel)
-> Text -> HeaderLevel -> HeaderLevel
forall a b. (a -> b) -> a -> b
$ Text
"Using headers is not allowed when section name is set small\n\
                              \Make sure docItemToMarkdown @" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> TypeRep -> Text
forall b a. (Show a, IsString b) => a -> b
show (Proxy d -> TypeRep
forall k (proxy :: k -> *) (a :: k).
Typeable a =>
proxy a -> TypeRep
typeRep (Proxy d -> TypeRep) -> Proxy d -> TypeRep
forall a b. (a -> b) -> a -> b
$ Proxy d
forall k (t :: k). Proxy t
Proxy @di) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
                              Text
"does not use its 'header level' argument"
                    )
        sectionDesc :: Maybe Markdown
sectionDesc = DocItem d => Maybe Markdown
forall d. DocItem d => Maybe Markdown
docItemSectionDescription @di
        sectionDescFull :: Markdown
sectionDescFull =
          case Maybe Markdown
sectionDesc of
            Maybe Markdown
Nothing -> Markdown
""
            Just Markdown
sd -> Markdown
sd Markdown -> Markdown -> Markdown
forall a. Semigroup a => a -> a -> a
<> Markdown
"\n\n"
        resItems :: [d]
resItems = [d] -> [d]
forall d. DocItem d => [d] -> [d]
docItemsOrder ([d] -> [d]) -> [d] -> [d]
forall a b. (a -> b) -> a -> b
$ (DocElem d -> d) -> [DocElem d] -> [d]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
map DocElem d -> d
forall d. DocElem d -> d
deItem ((NonEmpty $ DocElem d) -> [Element (NonEmpty $ DocElem d)]
forall t. Container t => t -> [Element t]
toList NonEmpty $ DocElem d
items)
        content :: Markdown
content =
          [Markdown] -> Markdown
forall a. Monoid a => [a] -> a
mconcat ([Markdown] -> Markdown) -> [Markdown] -> Markdown
forall a b. (a -> b) -> a -> b
$ [d]
resItems [d] -> (d -> Markdown) -> [Markdown]
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \d
di ->
            HeaderLevel -> d -> Markdown
forall d. DocItem d => HeaderLevel -> d -> Markdown
docItemToMarkdownFull (HeaderLevel -> HeaderLevel
headerLevelDelta HeaderLevel
hl) d
di
        anchor :: Markdown
anchor = Markdown
-> (SectionAnchor -> Markdown) -> Maybe SectionAnchor -> Markdown
forall b a. b -> (a -> b) -> Maybe a -> b
maybe Markdown
"" SectionAnchor -> Markdown
forall anchor. ToAnchor anchor => anchor -> Markdown
mdAnchor (DocItem d => Maybe SectionAnchor
forall di. DocItem di => Maybe SectionAnchor
docItemSectionAnchor @di)
    in if [d] -> Bool
forall t. Container t => t -> Bool
null [d]
resItems
       then Markdown
""
       else Markdown
anchor Markdown -> Markdown -> Markdown
forall a. Semigroup a => a -> a -> a
<> Markdown
sectionNameFull Markdown -> Markdown -> Markdown
forall a. Semigroup a => a -> a -> a
<> Markdown
sectionDescFull Markdown -> Markdown -> Markdown
forall a. Semigroup a => a -> a -> a
<> Markdown
content

-- | Anchor for all the sections (referring them as to headers may cause
-- colissions).
newtype SectionAnchor = SectionAnchor
  { SectionAnchor -> Text
_unSectionAnchor :: Text
    -- ^ Section name
  }

instance ToAnchor SectionAnchor where
  toAnchor :: SectionAnchor -> Anchor
toAnchor (SectionAnchor Text
t) = Text -> Anchor
Anchor (Text
"section-" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
t)

-- | Make an anchor that is to be attached to the given section.
docItemSectionAnchor :: forall di. DocItem di => Maybe SectionAnchor
docItemSectionAnchor :: Maybe SectionAnchor
docItemSectionAnchor = do
  case DocItem di => DocSectionNameStyle
forall d. DocItem d => DocSectionNameStyle
docItemSectionNameStyle @di of
    DocSectionNameStyle
DocSectionNameBig -> Maybe ()
forall (f :: * -> *). Applicative f => f ()
pass
    DocSectionNameStyle
DocSectionNameSmall -> Maybe ()
forall (m :: * -> *) a. MonadPlus m => m a
mzero
  Text -> SectionAnchor
SectionAnchor (Text -> SectionAnchor) -> Maybe Text -> Maybe SectionAnchor
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> DocItem di => Maybe Text
forall d. DocItem d => Maybe Text
docItemSectionName @di

-- | Reference to the given section.
--
-- Will return @Nothing@ if sections of given doc item type are not
-- assumed to be referred outside.
docItemSectionRef :: forall di. DocItem di => Maybe Markdown
docItemSectionRef :: Maybe Markdown
docItemSectionRef = do
  Text
name <- DocItem di => Maybe Text
forall d. DocItem d => Maybe Text
docItemSectionName @di
  SectionAnchor
anchor <- DocItem di => Maybe SectionAnchor
forall di. DocItem di => Maybe SectionAnchor
docItemSectionAnchor @di
  return $ Markdown -> SectionAnchor -> Markdown
forall anchor. ToAnchor anchor => Markdown -> anchor -> Markdown
mdLocalRef (Text -> Markdown
forall p. Buildable p => p -> Markdown
build Text
name) SectionAnchor
anchor

-- | Render a part of table of contents from 'DocBlock'.
docBlockToToc :: HeaderLevel -> DocBlock -> Markdown
docBlockToToc :: HeaderLevel -> DocBlock -> Markdown
docBlockToToc HeaderLevel
hl DocBlock
block =
  [Markdown] -> Markdown
forall a. Monoid a => [a] -> a
mconcat ([Markdown] -> Markdown) -> [Markdown] -> Markdown
forall a b. (a -> b) -> a -> b
$ DocBlock -> [DocSection]
forall k a. Map k a -> [a]
M.elems DocBlock
block [DocSection] -> (DocSection -> Markdown) -> [Markdown]
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> \(DocSection items :: NonEmpty $ DocElem d
items@((DocElem d
_ :: DocElem di) :| [DocElem d]
_)) ->
    let sectionName :: Maybe Text
sectionName = DocItem d => Maybe Text
forall d. DocItem d => Maybe Text
docItemSectionName @di
        (Markdown
sectionNameFull, HeaderLevel -> HeaderLevel
headerLevelDelta) =
          case (Maybe Text
sectionName, DocItem d => Maybe SectionAnchor
forall di. DocItem di => Maybe SectionAnchor
docItemSectionAnchor @di) of
            (Maybe Text
_, Maybe SectionAnchor
Nothing) -> (Markdown
"", HeaderLevel -> HeaderLevel
forall a. a -> a
id)
            (Maybe Text
Nothing, Maybe SectionAnchor
_) -> (Markdown
"", HeaderLevel -> HeaderLevel
forall a. a -> a
id)
            (Just Text
"Table of contents", Maybe SectionAnchor
_) -> (Markdown
"", HeaderLevel -> HeaderLevel
forall a. a -> a
id)
            (Just Text
sn, Just SectionAnchor
anchor) ->
              (HeaderLevel -> Markdown -> SectionAnchor -> Markdown
forall anchor.
ToAnchor anchor =>
HeaderLevel -> Markdown -> anchor -> Markdown
mdToc HeaderLevel
hl (Text -> Markdown
forall p. Buildable p => p -> Markdown
build Text
sn) SectionAnchor
anchor, HeaderLevel -> HeaderLevel
nextHeaderLevel)
        resItems :: [d]
resItems = [d] -> [d]
forall d. DocItem d => [d] -> [d]
docItemsOrder ([d] -> [d]) -> [d] -> [d]
forall a b. (a -> b) -> a -> b
$ (DocElem d -> d) -> [DocElem d] -> [d]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
map DocElem d -> d
forall d. DocElem d -> d
deItem ((NonEmpty $ DocElem d) -> [Element (NonEmpty $ DocElem d)]
forall t. Container t => t -> [Element t]
toList NonEmpty $ DocElem d
items)
        content :: Markdown
content =
          [Markdown] -> Markdown
forall a. Monoid a => [a] -> a
mconcat ([Markdown] -> Markdown) -> [Markdown] -> Markdown
forall a b. (a -> b) -> a -> b
$ [d]
resItems [d] -> (d -> Markdown) -> [Markdown]
forall (f :: * -> *) a b. Functor f => f a -> (a -> b) -> f b
<&> HeaderLevel -> d -> Markdown
forall d. DocItem d => HeaderLevel -> d -> Markdown
docItemToToc (HeaderLevel -> HeaderLevel
headerLevelDelta HeaderLevel
hl)
    in if [d] -> Bool
forall t. Container t => t -> Bool
null [d]
resItems
       then Markdown
""
       else Markdown
sectionNameFull Markdown -> Markdown -> Markdown
forall a. Semigroup a => a -> a -> a
<> Markdown
content

-- | Lift a doc item to a block, be it atomic doc item or grouping one.
docItemToBlockGeneral :: forall di. DocItem di => di -> Maybe SubDoc -> DocBlock
docItemToBlockGeneral :: di -> Maybe SubDoc -> DocBlock
docItemToBlockGeneral di
di Maybe SubDoc
msub =
  OneItem DocBlock -> DocBlock
forall x. One x => OneItem x -> x
one ( DocItem di => DocItemPos
forall d. DocItem d => DocItemPos
docItemPosition @di
      , (NonEmpty $ DocElem di) -> DocSection
forall d. DocItem d => (NonEmpty $ DocElem d) -> DocSection
DocSection ((NonEmpty $ DocElem di) -> DocSection)
-> (NonEmpty $ DocElem di) -> DocSection
forall a b. (a -> b) -> a -> b
$ OneItem (NonEmpty $ DocElem di) -> NonEmpty $ DocElem di
forall x. One x => OneItem x -> x
one (di -> Maybe SubDoc -> DocElem di
forall d. d -> Maybe SubDoc -> DocElem d
DocElem di
di Maybe SubDoc
msub)
      )

-- | Lift an atomic doc item to a block.
docItemToBlock :: forall di. DocItem di => di -> DocBlock
docItemToBlock :: di -> DocBlock
docItemToBlock di
di = di -> Maybe SubDoc -> DocBlock
forall di. DocItem di => di -> Maybe SubDoc -> DocBlock
docItemToBlockGeneral di
di Maybe SubDoc
forall a. Maybe a
Nothing

-- | Find all doc items of the given type.
lookupDocBlockSection :: forall d. DocItem d => DocBlock -> Maybe (NonEmpty d)
lookupDocBlockSection :: DocBlock -> Maybe (NonEmpty d)
lookupDocBlockSection DocBlock
block = do
  DocSection (NonEmpty (DocElem d)
ds :: NonEmpty (DocElem d')) <- DocItemPos -> DocBlock -> Maybe DocSection
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup (DocItem d => DocItemPos
forall d. DocItem d => DocItemPos
docItemPosition @d) DocBlock
block
  case (Typeable d, Typeable d) => Maybe (d :~: d)
forall k (a :: k) (b :: k).
(Typeable a, Typeable b) =>
Maybe (a :~: b)
eqT @d @d' of
    Maybe (d :~: d)
Nothing -> Text -> Maybe (NonEmpty d)
forall a. HasCallStack => Text -> a
error (Text -> Maybe (NonEmpty d)) -> Text -> Maybe (NonEmpty d)
forall a b. (a -> b) -> a -> b
$ Markdown
"Invalid DocBlock: item of type " Markdown -> Markdown -> Text
forall b. FromBuilder b => Markdown -> Markdown -> b
+|| Proxy d -> TypeRep
forall k (proxy :: k -> *) (a :: k).
Typeable a =>
proxy a -> TypeRep
typeRep (Proxy d
forall k (t :: k). Proxy t
Proxy @d) TypeRep -> Markdown -> Markdown
forall a b. (Show a, FromBuilder b) => a -> Markdown -> b
||+ Markdown
" \
                       \under position " Markdown -> Markdown -> Markdown
forall b. FromBuilder b => Markdown -> Markdown -> b
+| DocItem d => DocItemPos
forall d. DocItem d => DocItemPos
docItemPosition @d DocItemPos -> Markdown -> Markdown
forall a b. (Buildable a, FromBuilder b) => a -> Markdown -> b
|+ Markdown
""
    Just d :~: d
Refl -> NonEmpty d -> Maybe (NonEmpty d)
forall (f :: * -> *) a. Applicative f => a -> f a
pure (NonEmpty d -> Maybe (NonEmpty d))
-> NonEmpty d -> Maybe (NonEmpty d)
forall a b. (a -> b) -> a -> b
$ (DocElem d -> d) -> NonEmpty (DocElem d) -> NonEmpty d
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
map DocElem d -> d
forall d. DocElem d -> d
deItem NonEmpty (DocElem d)
ds

-- | A part of documentation to be grouped. Essentially incapsulates 'DocBlock'.

-- One day we may need to define 'Eq' instance for this thing, and probably
-- we can consider any two entities equal for efficiency.
newtype SubDoc = SubDoc DocBlock

-- | Render documentation for 'SubDoc'.
subDocToMarkdown :: HeaderLevel -> SubDoc -> Markdown
subDocToMarkdown :: HeaderLevel -> SubDoc -> Markdown
subDocToMarkdown HeaderLevel
hl (SubDoc DocBlock
d) = HeaderLevel -> DocBlock -> Markdown
docBlockToMarkdown HeaderLevel
hl DocBlock
d

-- | Render documentation for 'SubDoc'.
subDocToToc :: HeaderLevel -> SubDoc -> Markdown
subDocToToc :: HeaderLevel -> SubDoc -> Markdown
subDocToToc HeaderLevel
hl (SubDoc DocBlock
d) = HeaderLevel -> DocBlock -> Markdown
docBlockToToc HeaderLevel
hl DocBlock
d

-- | Keeps documentation gathered for some piece of contract code.
--
-- Used for building documentation of a contract.
data ContractDoc = ContractDoc
  { ContractDoc -> DocBlock
cdContents :: DocBlock
    -- ^ All inlined doc items.
  , ContractDoc -> DocBlock
cdDefinitions :: DocBlock
    -- ^ Definitions used in document.
    --
    -- Usually you put some large and repetitive descriptions here.
    -- This differs from the document content in that
    -- it contains sections which are always at top-level,
    -- disregard the nesting.
    --
    -- All doc items which define 'docItemId' method go here, and only they.
  , ContractDoc -> Set SomeDocDefinitionItem
cdDefinitionsSet :: Set SomeDocDefinitionItem
    -- ^ We remember all already declared entries to avoid cyclic dependencies
    -- in documentation items discovery.
  , ContractDoc -> Set DocItemId
cdDefinitionIds :: Set DocItemId
    -- ^ We remember all already used identifiers.
    -- (Documentation naturally should not declare multiple items with
    -- the same identifier because that would make references to the respective
    -- anchors ambiguous).
  }

makeLensesWith postfixLFields ''ContractDoc

-- | Contract documentation assembly primarily relies on this instance.
instance Semigroup ContractDoc where
  ContractDoc
cd1 <> :: ContractDoc -> ContractDoc -> ContractDoc
<> ContractDoc
cd2 = ContractDoc :: DocBlock
-> DocBlock
-> Set SomeDocDefinitionItem
-> Set DocItemId
-> ContractDoc
ContractDoc
    { cdContents :: DocBlock
cdContents =
        SimpleWhenMissing DocItemPos DocSection DocSection
-> SimpleWhenMissing DocItemPos DocSection DocSection
-> SimpleWhenMatched DocItemPos DocSection DocSection DocSection
-> DocBlock
-> DocBlock
-> DocBlock
forall k a c b.
Ord k =>
SimpleWhenMissing k a c
-> SimpleWhenMissing k b c
-> SimpleWhenMatched k a b c
-> Map k a
-> Map k b
-> Map k c
M.merge
          SimpleWhenMissing DocItemPos DocSection DocSection
forall (f :: * -> *) k x. Applicative f => WhenMissing f k x x
M.preserveMissing SimpleWhenMissing DocItemPos DocSection DocSection
forall (f :: * -> *) k x. Applicative f => WhenMissing f k x x
M.preserveMissing
          ((DocItemPos -> DocSection -> DocSection -> DocSection)
-> SimpleWhenMatched DocItemPos DocSection DocSection DocSection
forall (f :: * -> *) k x y z.
Applicative f =>
(k -> x -> y -> z) -> WhenMatched f k x y z
M.zipWithMatched ((DocItemPos -> DocSection -> DocSection -> DocSection)
 -> SimpleWhenMatched DocItemPos DocSection DocSection DocSection)
-> (DocItemPos -> DocSection -> DocSection -> DocSection)
-> SimpleWhenMatched DocItemPos DocSection DocSection DocSection
forall a b. (a -> b) -> a -> b
$ \DocItemPos
_k DocSection
l DocSection
r -> HasCallStack => DocSection -> DocSection -> DocSection
DocSection -> DocSection -> DocSection
unsafeAppendDocSection DocSection
l DocSection
r)
          (ContractDoc -> DocBlock
cdContents ContractDoc
cd1) (ContractDoc -> DocBlock
cdContents ContractDoc
cd2)
    , cdDefinitions :: DocBlock
cdDefinitions =
        SimpleWhenMissing DocItemPos DocSection DocSection
-> SimpleWhenMissing DocItemPos DocSection DocSection
-> SimpleWhenMatched DocItemPos DocSection DocSection DocSection
-> DocBlock
-> DocBlock
-> DocBlock
forall k a c b.
Ord k =>
SimpleWhenMissing k a c
-> SimpleWhenMissing k b c
-> SimpleWhenMatched k a b c
-> Map k a
-> Map k b
-> Map k c
M.merge
          SimpleWhenMissing DocItemPos DocSection DocSection
forall (f :: * -> *) k x. Applicative f => WhenMissing f k x x
M.preserveMissing SimpleWhenMissing DocItemPos DocSection DocSection
forall (f :: * -> *) k x. Applicative f => WhenMissing f k x x
M.preserveMissing
          ((DocItemPos -> DocSection -> DocSection -> DocSection)
-> SimpleWhenMatched DocItemPos DocSection DocSection DocSection
forall (f :: * -> *) k x y z.
Applicative f =>
(k -> x -> y -> z) -> WhenMatched f k x y z
M.zipWithMatched ((DocItemPos -> DocSection -> DocSection -> DocSection)
 -> SimpleWhenMatched DocItemPos DocSection DocSection DocSection)
-> (DocItemPos -> DocSection -> DocSection -> DocSection)
-> SimpleWhenMatched DocItemPos DocSection DocSection DocSection
forall a b. (a -> b) -> a -> b
$ \DocItemPos
_k (DocSection NonEmpty $ DocElem d
ls) (DocSection rs) ->
             let removeDups :: [DocElem d] -> [DocElem d]
removeDups = (DocElem d -> Bool) -> [DocElem d] -> [DocElem d]
forall a. (a -> Bool) -> [a] -> [a]
filter ((DocElem d -> Bool) -> [DocElem d] -> [DocElem d])
-> (DocElem d -> Bool) -> [DocElem d] -> [DocElem d]
forall a b. (a -> b) -> a -> b
$ Bool -> Bool
not (Bool -> Bool) -> (DocElem d -> Bool) -> DocElem d -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (d -> Set DocItemId -> Bool
forall d. DocItem d => d -> Set DocItemId -> Bool
`isDefinedIn` ContractDoc -> Set DocItemId
cdDefinitionIds ContractDoc
cd1) (d -> Bool) -> (DocElem d -> d) -> DocElem d -> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. DocElem d -> d
forall d. DocElem d -> d
deItem
             in (NonEmpty $ DocElem d) -> DocSection
forall d. DocItem d => (NonEmpty $ DocElem d) -> DocSection
DocSection ((NonEmpty $ DocElem d) -> DocSection)
-> (NonEmpty $ DocElem d) -> DocSection
forall a b. (a -> b) -> a -> b
$ (NonEmpty $ DocElem d) -> [DocElem d] -> NonEmpty $ DocElem d
forall d1 d2.
(Typeable d1, Typeable d2, HasCallStack) =>
NonEmpty d1 -> [d2] -> NonEmpty d1
unsafeAppendDocSectionImpl NonEmpty $ DocElem d
ls ([DocElem d] -> [DocElem d]
removeDups ([DocElem d] -> [DocElem d]) -> [DocElem d] -> [DocElem d]
forall a b. (a -> b) -> a -> b
$ (NonEmpty $ DocElem d) -> [Element (NonEmpty $ DocElem d)]
forall t. Container t => t -> [Element t]
toList NonEmpty $ DocElem d
rs)
          )
          (ContractDoc -> DocBlock
cdDefinitions ContractDoc
cd1) (ContractDoc -> DocBlock
cdDefinitions ContractDoc
cd2)
    , cdDefinitionsSet :: Set SomeDocDefinitionItem
cdDefinitionsSet =
        Set SomeDocDefinitionItem
-> Set SomeDocDefinitionItem -> Set SomeDocDefinitionItem
forall a. Ord a => Set a -> Set a -> Set a
S.union (ContractDoc -> Set SomeDocDefinitionItem
cdDefinitionsSet ContractDoc
cd1) (ContractDoc -> Set SomeDocDefinitionItem
cdDefinitionsSet ContractDoc
cd2)
    , cdDefinitionIds :: Set DocItemId
cdDefinitionIds =
        Set DocItemId -> Set DocItemId -> Set DocItemId
forall a. Ord a => Set a -> Set a -> Set a
S.union (ContractDoc -> Set DocItemId
cdDefinitionIds ContractDoc
cd1) (ContractDoc -> Set DocItemId
cdDefinitionIds ContractDoc
cd2)
    }
    where
      isDefinedIn :: DocItem d => d -> Set DocItemId -> Bool
      isDefinedIn :: d -> Set DocItemId -> Bool
isDefinedIn d
di Set DocItemId
defs =
        case d -> DocItemRef (DocItemPlacement d) (DocItemReferenced d)
forall d.
DocItem d =>
d -> DocItemRef (DocItemPlacement d) (DocItemReferenced d)
docItemRef d
di of
          DocItemRef (DocItemPlacement d) (DocItemReferenced d)
DocItemNoRef -> Bool
False
          DocItemRef DocItemId
docItemId -> DocItemId
docItemId DocItemId -> Set DocItemId -> Bool
forall a. Ord a => a -> Set a -> Bool
`S.member` Set DocItemId
defs
          DocItemRefInlined DocItemId
docItemId -> DocItemId
docItemId DocItemId -> Set DocItemId -> Bool
forall a. Ord a => a -> Set a -> Bool
`S.member` Set DocItemId
defs

instance Monoid ContractDoc where
  mempty :: ContractDoc
mempty = ContractDoc :: DocBlock
-> DocBlock
-> Set SomeDocDefinitionItem
-> Set DocItemId
-> ContractDoc
ContractDoc
    { cdContents :: DocBlock
cdContents = DocBlock
forall k a. Map k a
M.empty
    , cdDefinitions :: DocBlock
cdDefinitions = DocBlock
forall k a. Map k a
M.empty
    , cdDefinitionsSet :: Set SomeDocDefinitionItem
cdDefinitionsSet = Set SomeDocDefinitionItem
forall a. Set a
S.empty
    , cdDefinitionIds :: Set DocItemId
cdDefinitionIds = Set DocItemId
forall a. Set a
S.empty
    }

-- | Render given contract documentation to markdown document.
contractDocToMarkdown :: ContractDoc -> LText
contractDocToMarkdown :: ContractDoc -> LText
contractDocToMarkdown ContractDoc{DocBlock
Set SomeDocDefinitionItem
Set DocItemId
cdDefinitionIds :: Set DocItemId
cdDefinitionsSet :: Set SomeDocDefinitionItem
cdDefinitions :: DocBlock
cdContents :: DocBlock
cdDefinitionIds :: ContractDoc -> Set DocItemId
cdDefinitionsSet :: ContractDoc -> Set SomeDocDefinitionItem
cdDefinitions :: ContractDoc -> DocBlock
cdContents :: ContractDoc -> DocBlock
..} =
  let
    contents :: Markdown
contents =
      HeaderLevel -> DocBlock -> Markdown
docBlockToMarkdown (Int -> HeaderLevel
HeaderLevel Int
1) DocBlock
cdContents Markdown -> Markdown -> Markdown
forall a b. (Buildable a, FromBuilder b) => a -> Markdown -> b
|+ Markdown
"\n\n"
    definitions :: Markdown
definitions
      | DocBlock -> Bool
forall t. Container t => t -> Bool
null DocBlock
cdDefinitions = Markdown
""
      | Bool
otherwise =
        Markdown
"# Definitions\n\n" Markdown -> Markdown -> Markdown
forall b. FromBuilder b => Markdown -> Markdown -> b
+| HeaderLevel -> DocBlock -> Markdown
docBlockToMarkdown (Int -> HeaderLevel
HeaderLevel Int
2) DocBlock
cdDefinitions
    total :: LText
total = Markdown -> LText
forall b. FromBuilder b => Markdown -> b
fmt (Markdown
contents Markdown -> Markdown -> Markdown
forall a. Semigroup a => a -> a -> a
<> Markdown
definitions)
  in LText -> LText
LT.strip LText
total LText -> LText -> LText
forall a. Semigroup a => a -> a -> a
<> LText
"\n"

contractDocToToc :: ContractDoc -> Markdown
contractDocToToc :: ContractDoc -> Markdown
contractDocToToc ContractDoc{DocBlock
Set SomeDocDefinitionItem
Set DocItemId
cdDefinitionIds :: Set DocItemId
cdDefinitionsSet :: Set SomeDocDefinitionItem
cdDefinitions :: DocBlock
cdContents :: DocBlock
cdDefinitionIds :: ContractDoc -> Set DocItemId
cdDefinitionsSet :: ContractDoc -> Set SomeDocDefinitionItem
cdDefinitions :: ContractDoc -> DocBlock
cdContents :: ContractDoc -> DocBlock
..} =
  let
    contents :: Markdown
contents =
      HeaderLevel -> DocBlock -> Markdown
docBlockToToc (Int -> HeaderLevel
HeaderLevel Int
1) DocBlock
cdContents

    definitions :: Markdown
definitions
      | DocBlock -> Bool
forall t. Container t => t -> Bool
null DocBlock
cdDefinitions = Markdown
""
      | Bool
otherwise = Markdown
"\n**[Definitions](#definitions)**\n\n"
          Markdown -> Markdown -> Markdown
forall b. FromBuilder b => Markdown -> Markdown -> b
+| HeaderLevel -> DocBlock -> Markdown
docBlockToToc (Int -> HeaderLevel
HeaderLevel Int
2) DocBlock
cdDefinitions
  in Markdown
contents Markdown -> Markdown -> Markdown
forall a. Semigroup a => a -> a -> a
<> Markdown
definitions Markdown -> Markdown -> Markdown
forall a. Semigroup a => a -> a -> a
<> Markdown
"\n"

-- | A function which groups a piece of doc under one doc item.
type DocGrouping = SubDoc -> SomeDocItem

instance Show DocGrouping where
  show :: DocGrouping -> String
show DocGrouping
_ = String
"<doc grouping>"

-- | Apply given grouping to documentation being built.
docGroupContent :: DocGrouping -> ContractDoc -> ContractDoc
docGroupContent :: DocGrouping -> ContractDoc -> ContractDoc
docGroupContent DocGrouping
grouping ContractDoc
doc =
  ContractDoc
doc
  { cdContents :: DocBlock
cdContents =
      let sub :: SubDoc
sub = DocBlock -> SubDoc
SubDoc (ContractDoc -> DocBlock
cdContents ContractDoc
doc)
      in case DocGrouping
grouping SubDoc
sub of
           SomeDocItem d
d -> d -> Maybe SubDoc -> DocBlock
forall di. DocItem di => di -> Maybe SubDoc -> DocBlock
docItemToBlockGeneral d
d (SubDoc -> Maybe SubDoc
forall a. a -> Maybe a
Just SubDoc
sub)
  }

-- | Everything that contains doc items that can be used to render the
-- documentation.
class ContainsDoc a where
  -- | Gather documentation.
  --
  -- Calling this method directly is discouraged in prod, see 'buildDoc' instead.
  -- Using this method in tests is fine though.
  buildDocUnfinalized :: a -> ContractDoc

-- | Some contract languages may support documentation update.
class ContainsDoc a => ContainsUpdateableDoc a where
  -- | Modify all documentation items recursively.
  modifyDocEntirely :: (SomeDocItem -> SomeDocItem) -> a -> a

-- | Often there is some tuning recommended prior to rendering the contract,
-- like attaching git revision info; this type designates that those last changes
-- were applied.
--
-- For example, at Michelson level you may want to use 'attachDocCommons'.
--
-- If you want no special tuning (e.g. for tests), say that explicitly with
-- 'finalizedAsIs'.
newtype WithFinalizedDoc a = WithFinalizedDoc (Identity a)
  deriving newtype (a -> WithFinalizedDoc b -> WithFinalizedDoc a
(a -> b) -> WithFinalizedDoc a -> WithFinalizedDoc b
(forall a b. (a -> b) -> WithFinalizedDoc a -> WithFinalizedDoc b)
-> (forall a b. a -> WithFinalizedDoc b -> WithFinalizedDoc a)
-> Functor WithFinalizedDoc
forall a b. a -> WithFinalizedDoc b -> WithFinalizedDoc a
forall a b. (a -> b) -> WithFinalizedDoc a -> WithFinalizedDoc b
forall (f :: * -> *).
(forall a b. (a -> b) -> f a -> f b)
-> (forall a b. a -> f b -> f a) -> Functor f
<$ :: a -> WithFinalizedDoc b -> WithFinalizedDoc a
$c<$ :: forall a b. a -> WithFinalizedDoc b -> WithFinalizedDoc a
fmap :: (a -> b) -> WithFinalizedDoc a -> WithFinalizedDoc b
$cfmap :: forall a b. (a -> b) -> WithFinalizedDoc a -> WithFinalizedDoc b
Functor, Functor WithFinalizedDoc
a -> WithFinalizedDoc a
Functor WithFinalizedDoc
-> (forall a. a -> WithFinalizedDoc a)
-> (forall a b.
    WithFinalizedDoc (a -> b)
    -> WithFinalizedDoc a -> WithFinalizedDoc b)
-> (forall a b c.
    (a -> b -> c)
    -> WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc c)
-> (forall a b.
    WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc b)
-> (forall a b.
    WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc a)
-> Applicative WithFinalizedDoc
WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc b
WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc a
WithFinalizedDoc (a -> b)
-> WithFinalizedDoc a -> WithFinalizedDoc b
(a -> b -> c)
-> WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc c
forall a. a -> WithFinalizedDoc a
forall a b.
WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc a
forall a b.
WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc b
forall a b.
WithFinalizedDoc (a -> b)
-> WithFinalizedDoc a -> WithFinalizedDoc b
forall a b c.
(a -> b -> c)
-> WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc c
forall (f :: * -> *).
Functor f
-> (forall a. a -> f a)
-> (forall a b. f (a -> b) -> f a -> f b)
-> (forall a b c. (a -> b -> c) -> f a -> f b -> f c)
-> (forall a b. f a -> f b -> f b)
-> (forall a b. f a -> f b -> f a)
-> Applicative f
<* :: WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc a
$c<* :: forall a b.
WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc a
*> :: WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc b
$c*> :: forall a b.
WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc b
liftA2 :: (a -> b -> c)
-> WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc c
$cliftA2 :: forall a b c.
(a -> b -> c)
-> WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc c
<*> :: WithFinalizedDoc (a -> b)
-> WithFinalizedDoc a -> WithFinalizedDoc b
$c<*> :: forall a b.
WithFinalizedDoc (a -> b)
-> WithFinalizedDoc a -> WithFinalizedDoc b
pure :: a -> WithFinalizedDoc a
$cpure :: forall a. a -> WithFinalizedDoc a
$cp1Applicative :: Functor WithFinalizedDoc
Applicative, Applicative WithFinalizedDoc
a -> WithFinalizedDoc a
Applicative WithFinalizedDoc
-> (forall a b.
    WithFinalizedDoc a
    -> (a -> WithFinalizedDoc b) -> WithFinalizedDoc b)
-> (forall a b.
    WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc b)
-> (forall a. a -> WithFinalizedDoc a)
-> Monad WithFinalizedDoc
WithFinalizedDoc a
-> (a -> WithFinalizedDoc b) -> WithFinalizedDoc b
WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc b
forall a. a -> WithFinalizedDoc a
forall a b.
WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc b
forall a b.
WithFinalizedDoc a
-> (a -> WithFinalizedDoc b) -> WithFinalizedDoc b
forall (m :: * -> *).
Applicative m
-> (forall a b. m a -> (a -> m b) -> m b)
-> (forall a b. m a -> m b -> m b)
-> (forall a. a -> m a)
-> Monad m
return :: a -> WithFinalizedDoc a
$creturn :: forall a. a -> WithFinalizedDoc a
>> :: WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc b
$c>> :: forall a b.
WithFinalizedDoc a -> WithFinalizedDoc b -> WithFinalizedDoc b
>>= :: WithFinalizedDoc a
-> (a -> WithFinalizedDoc b) -> WithFinalizedDoc b
$c>>= :: forall a b.
WithFinalizedDoc a
-> (a -> WithFinalizedDoc b) -> WithFinalizedDoc b
$cp1Monad :: Applicative WithFinalizedDoc
Monad)

-- | Mark the code with doc as finalized without any changes.
finalizedAsIs :: a -> WithFinalizedDoc a
finalizedAsIs :: a -> WithFinalizedDoc a
finalizedAsIs = Identity a -> WithFinalizedDoc a
forall a. Identity a -> WithFinalizedDoc a
WithFinalizedDoc (Identity a -> WithFinalizedDoc a)
-> (a -> Identity a) -> a -> WithFinalizedDoc a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> Identity a
forall a. a -> Identity a
Identity

-- | Gather documenation.
buildDoc :: ContainsDoc a => WithFinalizedDoc a -> ContractDoc
buildDoc :: WithFinalizedDoc a -> ContractDoc
buildDoc (WithFinalizedDoc (Identity a
a)) = a -> ContractDoc
forall a. ContainsDoc a => a -> ContractDoc
buildDocUnfinalized a
a

-- | Construct and format documentation in textual form.
buildMarkdownDoc :: ContainsDoc a => WithFinalizedDoc a -> LText
buildMarkdownDoc :: WithFinalizedDoc a -> LText
buildMarkdownDoc = ContractDoc -> LText
contractDocToMarkdown (ContractDoc -> LText)
-> (WithFinalizedDoc a -> ContractDoc)
-> WithFinalizedDoc a
-> LText
forall b c a. (b -> c) -> (a -> b) -> a -> c
. WithFinalizedDoc a -> ContractDoc
forall a. ContainsDoc a => WithFinalizedDoc a -> ContractDoc
buildDoc

-- | Recursevly traverse doc items and modify those that match given type.
--
-- If mapper returns 'Nothing', doc item will remain unmodified.
modifyDoc
  :: (ContainsUpdateableDoc a, DocItem i1, DocItem i2)
  => (i1 -> Maybe i2) -> a -> a
modifyDoc :: (i1 -> Maybe i2) -> a -> a
modifyDoc i1 -> Maybe i2
mapper = (SomeDocItem -> SomeDocItem) -> a -> a
forall a.
ContainsUpdateableDoc a =>
(SomeDocItem -> SomeDocItem) -> a -> a
modifyDocEntirely SomeDocItem -> SomeDocItem
untypedMapper
  where
  untypedMapper :: SomeDocItem -> SomeDocItem
untypedMapper sdi :: SomeDocItem
sdi@(SomeDocItem d
di) = SomeDocItem -> Maybe SomeDocItem -> SomeDocItem
forall a. a -> Maybe a -> a
fromMaybe SomeDocItem
sdi (Maybe SomeDocItem -> SomeDocItem)
-> Maybe SomeDocItem -> SomeDocItem
forall a b. (a -> b) -> a -> b
$ do
    i1
di' <- d -> Maybe i1
forall a b. (Typeable a, Typeable b) => a -> Maybe b
cast d
di
    i2
newDi <- i1 -> Maybe i2
mapper i1
di'
    return (i2 -> SomeDocItem
forall d. DocItem d => d -> SomeDocItem
SomeDocItem i2
newDi)

----------------------------------------------------------------------------
-- Basic doc items
----------------------------------------------------------------------------

-- | General (meta-)information about the contract such as git
-- revision, contract's authors, etc. Should be relatively short (not
-- several pages) because it is put somewhere close to the beginning of
-- documentation.
newtype DGeneralInfoSection = DGeneralInfoSection SubDoc

instance DocItem DGeneralInfoSection where
  docItemPos :: Natural
docItemPos = Natural
1
  docItemSectionName :: Maybe Text
docItemSectionName = Maybe Text
forall a. Maybe a
Nothing
  docItemToMarkdown :: HeaderLevel -> DGeneralInfoSection -> Markdown
docItemToMarkdown HeaderLevel
lvl (DGeneralInfoSection SubDoc
subDoc) =
    HeaderLevel -> SubDoc -> Markdown
subDocToMarkdown HeaderLevel
lvl SubDoc
subDoc
  docItemToToc :: HeaderLevel -> DGeneralInfoSection -> Markdown
docItemToToc HeaderLevel
lvl (DGeneralInfoSection SubDoc
subDoc) =
    HeaderLevel -> SubDoc -> Markdown
subDocToToc HeaderLevel
lvl SubDoc
subDoc

-- | Give a name to document block.
data DName = DName Text SubDoc

instance DocItem DName where
  docItemPos :: Natural
docItemPos = Natural
3
  docItemSectionName :: Maybe Text
docItemSectionName = Maybe Text
forall a. Maybe a
Nothing
  docItemToMarkdown :: HeaderLevel -> DName -> Markdown
docItemToMarkdown HeaderLevel
lvl (DName Text
name SubDoc
doc) =
    HeaderLevel -> Markdown -> Markdown
mdHeader HeaderLevel
lvl (Text -> Markdown
forall p. Buildable p => p -> Markdown
build Text
name) Markdown -> Markdown -> Markdown
forall a. Semigroup a => a -> a -> a
<>
    HeaderLevel -> SubDoc -> Markdown
subDocToMarkdown (HeaderLevel -> HeaderLevel
nextHeaderLevel HeaderLevel
lvl) SubDoc
doc
  docItemToToc :: HeaderLevel -> DName -> Markdown
docItemToToc HeaderLevel
lvl (DName Text
_ SubDoc
doc) =
    HeaderLevel -> SubDoc -> Markdown
subDocToToc (HeaderLevel -> HeaderLevel
nextHeaderLevel HeaderLevel
lvl) SubDoc
doc

-- | This instance allows writing something like @docGroup "Title"@,
-- this makes sense as the most primitive and basic use case for doc groups
-- is putting a section under name.
instance (di ~ DName) => IsString (SubDoc -> di) where
  fromString :: String -> SubDoc -> di
fromString = Text -> SubDoc -> DName
DName (Text -> SubDoc -> DName)
-> (String -> Text) -> String -> SubDoc -> DName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Text
forall a. IsString a => String -> a
fromString

-- | Description of something.
data DDescription = DDescription Markdown

instance DocItem DDescription where
  docItemPos :: Natural
docItemPos = Natural
10
  docItemSectionName :: Maybe Text
docItemSectionName = Maybe Text
forall a. Maybe a
Nothing
  docItemToMarkdown :: HeaderLevel -> DDescription -> Markdown
docItemToMarkdown HeaderLevel
_ (DDescription Markdown
txt) = Markdown -> Markdown
forall p. Buildable p => p -> Markdown
build Markdown
txt

-- | Specify version if given contract.
data DGitRevisionInfo = DGitRevisionInfo
  { DGitRevisionInfo -> GitRepoSettings
dgrRepoSettings :: GitRepoSettings
  , DGitRevisionInfo -> Text
dgrCommitSha :: Text
  , DGitRevisionInfo -> Text
dgrCommitDate :: Text
  }

data DGitRevision
  = DGitRevisionKnown DGitRevisionInfo
  | DGitRevisionUnknown

-- | Repository settings for 'DGitRevision'.
newtype GitRepoSettings = GitRepoSettings
  { GitRepoSettings -> Text -> Text
grsMkGitRevision :: Text -> Text
    -- ^ By commit sha make up a url to that commit in remote repository.

    -- @martoon: I tried to get remote URL automatically, but failed to
    -- find a way. Even "git-link" in emacs performs complex parsing.
  }

morleyRepoSettings :: GitRepoSettings
morleyRepoSettings :: GitRepoSettings
morleyRepoSettings = (Text -> Text) -> GitRepoSettings
GitRepoSettings ((Text -> Text) -> GitRepoSettings)
-> (Text -> Text) -> GitRepoSettings
forall a b. (a -> b) -> a -> b
$ \Text
commit ->
  Text
"https://gitlab.com/morley-framework/morley/-/tree/" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
commit

-- | Make 'DGitRevision'.
--
-- >>> :t $mkDGitRevision
-- ... :: GitRepoSettings -> DGitRevision
mkDGitRevision :: TH.ExpQ
mkDGitRevision :: ExpQ
mkDGitRevision = [e| \dgrRepoSettings ->
  maybe DGitRevisionUnknown DGitRevisionKnown $
    -- TH does not like do-blocks
    $(pickInfo gitHash "MORLEY_DOC_GIT_COMMIT_SHA") >>= \dgrCommitSha ->
    $(pickInfo gitCommitDate "MORLEY_DOC_GIT_COMMIT_DATE") >>= \dgrCommitDate ->
    return DGitRevisionInfo{..}
  |]
  where
    pickInfo :: ExpQ -> String -> ExpQ
pickInfo ExpQ
a String
b = Maybe String -> ExpQ
forall t. Lift t => t -> ExpQ
TH.lift (Maybe String -> ExpQ) -> Q (Maybe String) -> ExpQ
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< ExpQ -> String -> Q (Maybe String)
pickInfo' ExpQ
a String
b

    pickInfo' :: TH.ExpQ -> String -> TH.Q (Maybe String)
    pickInfo' :: ExpQ -> String -> Q (Maybe String)
pickInfo' ExpQ
askGit String
envKey =
      IO (Maybe String) -> Q (Maybe String)
forall (m :: * -> *) a. MonadIO m => IO a -> m a
liftIO (String -> IO (Maybe String)
lookupEnv String
envKey) Q (Maybe String)
-> (Maybe String -> Q (Maybe String)) -> Q (Maybe String)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
        Just String
"UNSPECIFIED" -> Maybe String -> Q (Maybe String)
forall (m :: * -> *) a. Monad m => a -> m a
return Maybe String
forall a. Maybe a
Nothing
        Just String
envValue -> Maybe String -> Q (Maybe String)
forall (m :: * -> *) a. Monad m => a -> m a
return (Maybe String -> Q (Maybe String))
-> Maybe String -> Q (Maybe String)
forall a b. (a -> b) -> a -> b
$ String -> Maybe String
forall a. a -> Maybe a
Just String
envValue
        Maybe String
Nothing -> ExpQ
askGit ExpQ -> (Exp -> Q (Maybe String)) -> Q (Maybe String)
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= \case
          -- Looks like with GitRev package we can't do anything better
          TH.LitE (TH.StringL String
"UNKNOWN") -> do
            String -> Q ()
TH.reportWarning (String -> Q ()) -> String -> Q ()
forall a b. (a -> b) -> a -> b
$
              String
"Contract autodoc: \
              \Not including git repository info because it cannot be deduced. \
              \Either provide repository environment, or pass '" String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
envKey String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
"' \
              \environmental variable."
            return Maybe String
forall a. Maybe a
Nothing
          TH.LitE (TH.StringL String
str) -> Maybe String -> Q (Maybe String)
forall (m :: * -> *) a. Monad m => a -> m a
return (String -> Maybe String
forall a. a -> Maybe a
Just String
str)
          Exp
value -> Text -> Q (Maybe String)
forall a. HasCallStack => Text -> a
error (Text -> Q (Maybe String)) -> Text -> Q (Maybe String)
forall a b. (a -> b) -> a -> b
$ Text
"Unknown value returned by git: " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Exp -> Text
forall b a. (Show a, IsString b) => a -> b
show Exp
value

instance DocItem DGitRevision where
  docItemPos :: Natural
docItemPos = Natural
2
  docItemSectionName :: Maybe Text
docItemSectionName = Maybe Text
forall a. Maybe a
Nothing
  docItemToMarkdown :: HeaderLevel -> DGitRevision -> Markdown
docItemToMarkdown HeaderLevel
_ (DGitRevisionKnown DGitRevisionInfo{Text
GitRepoSettings
dgrCommitDate :: Text
dgrCommitSha :: Text
dgrRepoSettings :: GitRepoSettings
dgrCommitDate :: DGitRevisionInfo -> Text
dgrCommitSha :: DGitRevisionInfo -> Text
dgrRepoSettings :: DGitRevisionInfo -> GitRepoSettings
..}) =
    [Markdown] -> Markdown
forall a. Monoid a => [a] -> a
mconcat
    [ Markdown -> Markdown -> Markdown
mdSubsection Markdown
"Code revision" (Markdown -> Markdown) -> Markdown -> Markdown
forall a b. (a -> b) -> a -> b
$
        let link :: Text
link = GitRepoSettings -> Text -> Text
grsMkGitRevision GitRepoSettings
dgrRepoSettings Text
dgrCommitSha
        in [Markdown] -> Markdown
forall a. Monoid a => [a] -> a
mconcat
           [ Markdown -> Markdown -> Markdown
mdRef (Text -> Markdown
forall p. Buildable p => p -> Markdown
build (Text -> Markdown) -> Text -> Markdown
forall a b. (a -> b) -> a -> b
$ Int -> Text -> Text
T.take Int
7 Text
dgrCommitSha) (Text -> Markdown
forall p. Buildable p => p -> Markdown
build Text
link)
           , Markdown
" "
           , Markdown -> Markdown
mdItalic (Markdown -> Markdown) -> Markdown -> Markdown
forall a b. (a -> b) -> a -> b
$ Markdown
"(" Markdown -> Markdown -> Markdown
forall a. Semigroup a => a -> a -> a
<> Text -> Markdown
forall p. Buildable p => p -> Markdown
build Text
dgrCommitDate Markdown -> Markdown -> Markdown
forall a. Semigroup a => a -> a -> a
<> Markdown
")"
           ]
    ]
  docItemToMarkdown HeaderLevel
_ DGitRevision
DGitRevisionUnknown = Markdown
""

-- | Comment in the doc (mostly used for licenses)
data DComment = DComment Text

instance DocItem DComment where
  docItemPos :: Natural
docItemPos = Natural
0
  docItemSectionName :: Maybe Text
docItemSectionName = Maybe Text
forall a. Maybe a
Nothing
  docItemToMarkdown :: HeaderLevel -> DComment -> Markdown
docItemToMarkdown HeaderLevel
_ (DComment Text
commentText) =
    Markdown
"<!---\n" Markdown -> Markdown -> Markdown
forall b. FromBuilder b => Markdown -> Markdown -> b
+| Text
commentText Text -> Markdown -> Markdown
forall a b. (Buildable a, FromBuilder b) => a -> Markdown -> b
|+ Markdown
"\n-->"

-- | @Table of contents@ to be inserted into the doc in an ad-hoc way.
--
-- It is not intended to be inserted manually. See 'attachToc' to understand
-- how this works.
--
data DToc = DToc Markdown

instance DocItem DToc where
  docItemPos :: Natural
docItemPos = Natural
11
  docItemSectionName :: Maybe Text
docItemSectionName = Text -> Maybe Text
forall a. a -> Maybe a
Just Text
"Table of contents"
  docItemToMarkdown :: HeaderLevel -> DToc -> Markdown
docItemToMarkdown HeaderLevel
_ (DToc Markdown
toc) = Markdown
toc

-- | A hand-made anchor.
data DAnchor = DAnchor Anchor

instance DocItem DAnchor where
  docItemPos :: Natural
docItemPos = Natural
4
  docItemSectionName :: Maybe Text
docItemSectionName = Maybe Text
forall a. Maybe a
Nothing
  docItemToMarkdown :: HeaderLevel -> DAnchor -> Markdown
docItemToMarkdown HeaderLevel
_ (DAnchor Anchor
a) = Anchor -> Markdown
forall anchor. ToAnchor anchor => anchor -> Markdown
mdAnchor Anchor
a

data DConversionInfo = DConversionInfo

-- TODO: we should also recommend using morley-client when
-- it'll become good enough
instance DocItem DConversionInfo where
  docItemPos :: Natural
docItemPos = Natural
15
  docItemSectionName :: Maybe Text
docItemSectionName = Text -> Maybe Text
forall a. a -> Maybe a
Just Text
"Haskell ⇄ Michelson conversion"
  docItemToMarkdown :: HeaderLevel -> DConversionInfo -> Markdown
docItemToMarkdown HeaderLevel
_ DConversionInfo
_ =
    Markdown
"This smart contract is developed in Haskell using the \
    \[Morley framework](https://gitlab.com/morley-framework/morley). \
    \Documentation mentions Haskell types that can be used for interaction with \
    \this contract from Haskell, but for each Haskell type we also mention its \
    \Michelson representation to make interactions outside of Haskell possible.\n\n\
    \There are multiple ways to interact with this contract:\n\n\
    \* Use this contract in your Haskell application, thus all operation submissions \
    \should be handled separately, e.g. via calling `tezos-client`, which will communicate \
    \with the `tezos-node`. In order to be able to call `tezos-client` you'll need to be able \
    \to construct Michelson values from Haskell.\n\n\
    \  The easiest way to do that is to serialize Haskell value using `lPackValue` function \
    \from [`Lorentz.Pack`](https://gitlab.com/morley-framework/morley/-/blob/2441e26bebd22ac4b30948e8facbb698d3b25c6d/code/lorentz/src/Lorentz/Pack.hs) \
    \module, encode resulting bytestring to hexadecimal representation using `encodeHex` function. \
    \Resulting hexadecimal encoded bytes sequence can be decoded back to Michelson value via \
    \`tezos-client unpack michelson data`.\n\n\
    \  Reverse conversion from Michelson value to the \
    \Haskell value can be done by serializing Michelson value using `tezos-client hash data` command, \
    \resulting `Raw packed data` should be decoded from the hexadecimal representation using `decodeHex` \
    \and deserialized to the Haskell value via `lUnpackValue` function from \
    \[`Lorentz.Pack`](https://gitlab.com/morley-framework/morley/-/blob/2441e26bebd22ac4b30948e8facbb698d3b25c6d/code/lorentz/src/Lorentz/Pack.hs).\n\n\
    \* Construct values for this contract directly on Michelson level using types provided in the \
    \documentation."

-- | Attach information about git revision.
-- The code must contain git revision placeholder.
--
-- We do this in two stages because we use TH to deduce git revision information
-- at compile time, and this is best to be done in the very end to recompile
-- less modules.
attachGitInfo :: ContainsUpdateableDoc a => DGitRevision -> a -> WithFinalizedDoc a
attachGitInfo :: DGitRevision -> a -> WithFinalizedDoc a
attachGitInfo DGitRevision
gitRev = a -> WithFinalizedDoc a
forall a. a -> WithFinalizedDoc a
finalizedAsIs (a -> WithFinalizedDoc a)
-> ((DGitRevision -> Maybe DGitRevision) -> a -> a)
-> (DGitRevision -> Maybe DGitRevision)
-> a
-> WithFinalizedDoc a
forall a b c. SuperComposition a b c => a -> b -> c
... (DGitRevision -> Maybe DGitRevision) -> a -> a
forall a i1 i2.
(ContainsUpdateableDoc a, DocItem i1, DocItem i2) =>
(i1 -> Maybe i2) -> a -> a
modifyDoc ((DGitRevision -> Maybe DGitRevision) -> a -> WithFinalizedDoc a)
-> (DGitRevision -> Maybe DGitRevision) -> a -> WithFinalizedDoc a
forall a b. (a -> b) -> a -> b
$ \case
  DGitRevision
DGitRevisionUnknown -> DGitRevision -> Maybe DGitRevision
forall a. a -> Maybe a
Just DGitRevision
gitRev
  DGitRevision
_ -> Maybe DGitRevision
forall a. Maybe a
Nothing

attachToc :: ContainsUpdateableDoc a => DToc -> a -> WithFinalizedDoc a
attachToc :: DToc -> a -> WithFinalizedDoc a
attachToc DToc
toc = a -> WithFinalizedDoc a
forall a. a -> WithFinalizedDoc a
finalizedAsIs (a -> WithFinalizedDoc a)
-> ((DToc -> Maybe DToc) -> a -> a)
-> (DToc -> Maybe DToc)
-> a
-> WithFinalizedDoc a
forall a b c. SuperComposition a b c => a -> b -> c
... (DToc -> Maybe DToc) -> a -> a
forall a i1 i2.
(ContainsUpdateableDoc a, DocItem i1, DocItem i2) =>
(i1 -> Maybe i2) -> a -> a
modifyDoc ((DToc -> Maybe DToc) -> a -> WithFinalizedDoc a)
-> (DToc -> Maybe DToc) -> a -> WithFinalizedDoc a
forall a b. (a -> b) -> a -> b
$ \case
  DToc Markdown
"" -> DToc -> Maybe DToc
forall a. a -> Maybe a
Just DToc
toc
  DToc
_ -> Maybe DToc
forall a. Maybe a
Nothing

-- | Attach common information that is available only in the end.
attachDocCommons :: ContainsUpdateableDoc a => DGitRevision -> a -> WithFinalizedDoc a
attachDocCommons :: DGitRevision -> a -> WithFinalizedDoc a
attachDocCommons DGitRevision
gitRev a
code = do
  let toc :: DToc
toc = Markdown -> DToc
DToc (Markdown -> DToc) -> Markdown -> DToc
forall a b. (a -> b) -> a -> b
$ ContractDoc -> Markdown
contractDocToToc (a -> ContractDoc
forall a. ContainsDoc a => a -> ContractDoc
buildDocUnfinalized a
code)
  a -> WithFinalizedDoc a
forall (f :: * -> *) a. Applicative f => a -> f a
pure a
code
    WithFinalizedDoc a
-> (a -> WithFinalizedDoc a) -> WithFinalizedDoc a
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= DGitRevision -> a -> WithFinalizedDoc a
forall a.
ContainsUpdateableDoc a =>
DGitRevision -> a -> WithFinalizedDoc a
attachGitInfo DGitRevision
gitRev
    WithFinalizedDoc a
-> (a -> WithFinalizedDoc a) -> WithFinalizedDoc a
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= DToc -> a -> WithFinalizedDoc a
forall a.
ContainsUpdateableDoc a =>
DToc -> a -> WithFinalizedDoc a
attachToc DToc
toc