lorentz-0.5.0: src/Lorentz/UStore/Doc.hs
-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ
{-# OPTIONS_GHC -Wno-orphans #-}
-- | Autodoc for UStore.
module Lorentz.UStore.Doc
( UStoreTemplateHasDoc (..)
, UStoreMarkerHasDoc (..)
, DUStoreTemplate (..)
, dUStoreTemplateRef
, DocumentTW
) where
import Data.Constraint (Dict(..))
import Fmt (build)
import Lorentz.Doc
import Lorentz.UStore.Traversal
import Lorentz.UStore.Types
import Michelson.Typed.Haskell.Doc
import Util.Generic
import Util.Label
import Util.Markdown
import Util.Typeable
import Util.TypeLits
-- | Information for UStore template required for documentation.
--
-- You only need to instantiate this for templates used directly in
-- UStore, nested subtemplates do not need this instance.
class Typeable template => UStoreTemplateHasDoc template where
-- | UStore template name as it appears in documentation.
--
-- Should be only 1 word.
ustoreTemplateDocName :: Text
default ustoreTemplateDocName
:: (Generic template, KnownSymbol (GenericTypeName template))
=> Text
ustoreTemplateDocName = symbolValT' @(GenericTypeName template)
where _needGeneric = Dict @(Generic template)
-- | Description of template.
ustoreTemplateDocDescription :: Markdown
-- | Description of template entries.
ustoreTemplateDocContents :: Markdown
default ustoreTemplateDocContents
:: (UStoreTraversable DocumentTW template) => Markdown
ustoreTemplateDocContents =
"\n" <> documentUStore (Proxy @template)
ustoreTemplateDocDependencies :: [SomeTypeWithDoc]
default ustoreTemplateDocDependencies
:: (UStoreTraversable DocumentTW template) => [SomeTypeWithDoc]
ustoreTemplateDocDependencies =
gatherUStoreDeps (Proxy @template)
-- | Instantiated for documented UStore markers.
class (KnownUStoreMarker marker) =>
UStoreMarkerHasDoc (marker :: UStoreMarkerType) where
-- | Specifies key encoding.
--
-- You accept description of field name, and should return how is it encoded
-- as key of @big_map bytes bytes@.
ustoreMarkerKeyEncoding :: Text -> Text
instance UStoreTemplateHasDoc template =>
TypeHasDoc (UStore template) where
typeDocName _ = "Upgradeable storage"
typeDocMdDescription = [md|
Storage with not hardcoded structure, which allows upgrading the contract
in place. UStore is capable of storing simple fields and multiple submaps.
|]
typeDocMdReference tp wp =
applyWithinParens wp $ mconcat
[ mdLocalRef (mdTicked "UStore") (docItemRef (DType tp))
, " "
, dUStoreTemplateRef (DUStoreTemplate (Proxy @template))
]
typeDocHaskellRep = homomorphicTypeDocHaskellRep
typeDocMichelsonRep = homomorphicTypeDocMichelsonRep
typeDocDependencies p =
genericTypeDocDependencies p <>
[SomeDocDefinitionItem (DUStoreTemplate $ Proxy @template)]
data DUStoreTemplate where
DUStoreTemplate
:: UStoreTemplateHasDoc template
=> Proxy template -> DUStoreTemplate
instance Eq DUStoreTemplate where
DUStoreTemplate p1 == DUStoreTemplate p2 = eqParam1 p1 p2
instance Ord DUStoreTemplate where
DUStoreTemplate p1 `compare` DUStoreTemplate p2 = compareExt p1 p2
instance DocItem DUStoreTemplate where
type DocItemPlacement DUStoreTemplate = 'DocItemInDefinitions
docItemPos = 12700
docItemSectionName = Just "Used upgradeable storage formats"
docItemSectionDescription = Just
"This section describes formats (aka _templates_) of upgradeable storages \
\mentioned across the given document. \
\Each format describes set of fields and virtual submaps which the storage \
\must have."
docItemToMarkdown lvl (DUStoreTemplate (_ :: Proxy template)) =
mconcat
[ mdSeparator
, mdHeader lvl (mdTicked . build $ ustoreTemplateDocName @template)
, ustoreTemplateDocDescription @template
, "\n\n"
, mdSubsection "Contents" $ ustoreTemplateDocContents @template
, "\n\n"
]
docItemRef (DUStoreTemplate (_ :: Proxy template)) =
DocItemRef . DocItemId $
"ustore-template-" <> ustoreTemplateDocName @template
docItemDependencies (DUStoreTemplate (_ :: Proxy template)) =
ustoreTemplateDocDependencies @template <&>
\(SomeTypeWithDoc t) -> SomeDocDefinitionItem (DType t)
-- | Make a reference to given UStore template description.
dUStoreTemplateRef :: DUStoreTemplate -> Markdown
dUStoreTemplateRef (DUStoreTemplate (_ :: Proxy template)) =
mdLocalRef (mdTicked . build $ ustoreTemplateDocName @template)
(docItemRef (DUStoreTemplate (Proxy @template)))
-- Instances
----------------------------------------------------------------------------
instance UStoreTemplateHasDoc () where
ustoreTemplateDocName = "empty"
ustoreTemplateDocDescription = ""
ustoreTemplateDocContents =
mdItalic "empty"
instance UStoreMarkerHasDoc UMarkerPlainField where
ustoreMarkerKeyEncoding k = "pack (" <> k <> ")"
-- Internals
----------------------------------------------------------------------------
documentUStore
:: forall template.
(UStoreTraversable DocumentTW template)
=> Proxy template -> Markdown
documentUStore _ =
let Const collected = traverseUStore @_ @template DocumentTW (Const ())
entries = appEndo (dcEntries collected) []
in if null entries
then mdTicked "<empty>"
else mconcat $ map (\e -> "* " <> e <> "\n") entries
gatherUStoreDeps
:: forall template.
(UStoreTraversable DocumentTW template)
=> Proxy template -> [SomeTypeWithDoc]
gatherUStoreDeps _ =
let Const collected = traverseUStore @_ @template DocumentTW (Const ())
in appEndo (dcDependencies collected) []
data DocCollector = DocCollector
{ dcEntries :: Endo [Markdown]
, dcDependencies :: Endo [SomeTypeWithDoc]
}
data DocumentTW = DocumentTW
instance Semigroup DocCollector where
DocCollector e1 d1 <> DocCollector e2 d2 = DocCollector (e1 <> e2) (d1 <> d2)
instance Monoid DocCollector where
mempty = DocCollector mempty mempty
instance UStoreTraversalWay DocumentTW where
type UStoreTraversalArgumentWrapper DocumentTW = Const ()
type UStoreTraversalMonad DocumentTW = Const DocCollector
instance (UStoreMarkerHasDoc marker, TypeHasDoc v) =>
UStoreTraversalFieldHandler DocumentTW marker v where
ustoreTraversalFieldHandler DocumentTW fieldName (Const ()) =
Const DocCollector
{ dcEntries = Endo . (:) $ mconcat
[ mdBold "Field" <> " "
, mdTicked (build $ labelToText fieldName)
, ": "
, typeDocMdReference (Proxy @v) (WithinParens False)
, "\n"
, mdSpoiler "Encoding" $ mconcat
[ "\n"
, let key = build $
ustoreMarkerKeyEncoding @marker
("\"" <> labelToText fieldName <> "\"")
in
" + " <> mdTicked ("key = " <> key) <> "\n\n"
, " + " <> mdTicked "value = pack (<field value>)" <> "\n\n"
, mdSeparator
]
, "\n"
]
, dcDependencies = Endo . (<>) $
[ SomeTypeWithDoc (Proxy @v)
]
}
instance (TypeHasDoc k, TypeHasDoc v) =>
UStoreTraversalSubmapHandler DocumentTW k v where
ustoreTraversalSubmapHandler DocumentTW fieldName (Const ()) =
Const DocCollector
{ dcEntries = Endo . (:) $ mconcat
[ mdBold "Submap" <> " "
, mdTicked (build $ labelToText fieldName)
, ": "
, typeDocMdReference (Proxy @k) (WithinParens False)
, " -> "
, typeDocMdReference (Proxy @v) (WithinParens False)
, "\n"
, mdSpoiler "Encoding" $ mconcat
[ "\n"
, " + " <> mdTicked "key = pack (<submap key>)" <> "\n\n"
, " + " <> mdTicked "value = pack (<submap value>)" <> "\n\n"
, mdSeparator
]
, "\n"
]
, dcDependencies = Endo . (<>) $
[ SomeTypeWithDoc (Proxy @k)
, SomeTypeWithDoc (Proxy @v)
]
}