packages feed

ginger2-2.2.0.0: src/Language/Ginger/BuiltinsAutodoc.hs

{-# LANGUAGE OverloadedStrings #-}

module Language.Ginger.BuiltinsAutodoc
where

import Control.Monad.Identity (Identity, runIdentity)
import qualified Data.Map.Strict as Map
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Vector as Vector
import Language.Haskell.TH (getDoc, putDoc, DocLoc (..), DecsQ, runIO)

import Language.Ginger.Interpret
import Language.Ginger.Interpret.Builtins
    ( builtinGlobals
    , builtinGlobalsNonJinja
    , builtinBoolAttribs
    , builtinIntAttribs
    , builtinFloatAttribs
    , builtinStringAttribs
    , builtinListAttribs
    , builtinDictAttribs
    , BuiltinAttribs
    )
import Language.Ginger.Interpret.DefEnv
    ( builtinTests
    , builtinFilters
    )
import Language.Ginger.AST
import Language.Ginger.Value

markdownToHaddock :: Text -> Text
markdownToHaddock =
  Text.replace "'" "\\'" .
  Text.replace "\"" "\\\"" .
  Text.replace "`" "@" .
  Text.replace "```" "@"

addHaddockFromFile :: FilePath -> DecsQ
addHaddockFromFile path = do
  src <- runIO $ readFile path
  doc <- fromMaybe "" <$> getDoc ModuleDoc
  putDoc ModuleDoc $ doc ++ src
  pure []

extractAttribs :: Monoid a
               => BuiltinAttribs a Identity
               -> [(Identifier, Value Identity)]
extractAttribs =
  extractAttribsWith mempty

extractAttribsWith :: a
                   -> BuiltinAttribs a Identity
                   -> [(Identifier, Value Identity)]
extractAttribsWith dummy =
  Map.toAscList .
  fmap (
    either (error . show) id .
    runIdentity .
    ($ dummy)
  )

builtinsAutodoc :: DecsQ
builtinsAutodoc = do
  doc <- fromMaybe "" <$> getDoc ModuleDoc
  putDoc ModuleDoc $
    doc ++
    "\n\n== __List Of Builtin Globals__\n" ++
    "\nThese are available in Jinja, and work (mostly) the same in Ginger.\n" ++
    unlines (map (goItem Nothing "globals_jinja_") (Map.toAscList $ builtinGlobals evalE)) ++

    "\n\n== __List Of Extension Globals__\n" ++
    "\nThese are not available in Jinja\n" ++
    unlines (map (goItem Nothing "globals_ginger_") (Map.toAscList $ builtinGlobalsNonJinja evalE)) ++

    "\n\n== __List Of Builtin Attributes__\n" ++
    "\n\n=== Bool\n" ++
    unlines
      (map
        (goItem (Just "bool") "globals_bool_")
        (extractAttribsWith False builtinBoolAttribs)) ++
    "\n\n=== Int\n" ++
    unlines
      (map
        (goItem (Just "int") "globals_int_")
        (extractAttribsWith 0 builtinIntAttribs)) ++
    "\n\n=== Float\n" ++
    unlines
      (map
        (goItem (Just "float") "globals_float_")
        (extractAttribsWith 0 builtinFloatAttribs)) ++
    "\n\n=== String\n" ++
    unlines
      (map
        (goItem (Just "string") "globals_string_")
        (extractAttribs builtinStringAttribs)) ++
    "\n\n=== List\n" ++
    unlines
      (map
        (goItem (Just "list") "globals_list_")
        (extractAttribs builtinListAttribs)) ++
    "\n\n=== Dict\n" ++
    unlines
      (map
        (goItem (Just "dict") "globals_dict_")
        (extractAttribs builtinDictAttribs)) ++

    "\n\n== __List Of Builtin Filters__\n" ++
    "\nThese will only work in a filter context, not via procedure call syntax.\n" ++
    unlines (map (goItem Nothing "filters_") (Map.toAscList $ builtinFilters)) ++

    "\n\n== __List Of Builtin Tests__\n" ++
    "\nThese will only work in a test context (e.g., an @is@-expression).\n\n" ++
    "\nSome of these tests shadow globals of the same name but different functionality.\n\n" ++
    unlines (map (goItem Nothing "tests_") (Map.toAscList $ builtinTests))
  pure []
  where
    goTy :: TypeDoc -> Text
    goTy TypeDocAny = "any"
    goTy TypeDocNone = "none"
    goTy (TypeDocSingle t) = t
    goTy (TypeDocAlternatives ts) =
      "[" <> Text.intercalate " | " (Vector.toList $ ts) <> "]"

    goItemHeading :: Maybe Text -> Text -> Text -> [Text]
    goItemHeading namespaceMay prefix name =
        [ ""
        , "#" <> prefix <> maybe "" (<> ".") namespaceMay <> name <> "#"
        , ""
        , "=== " <> maybe "" (<> ".") namespaceMay <> name
        ]

    goArgSig :: ArgumentDoc -> Text
    goArgSig arg =
      argumentDocName arg <>
      maybe "" ("=" <>) (argumentDocDefault arg) <>
      maybe "" ((" : " <>) . goTy) (argumentDocType arg)

    goArgDesc :: ArgumentDoc -> [Text]
    goArgDesc arg =
      [ "[@" <> argumentDocName arg <> "@]:" <> argumentDocDescription arg ]

    goDocumentedItem :: Maybe Text -> Text -> (Identifier, ProcedureDoc) -> String
    goDocumentedItem namespaceMay prefix (name, d) =
      let qualifiedName = maybe "" (<> ".") namespaceMay <> identifierName name
      in
        Text.unpack . Text.unlines $
          goItemHeading namespaceMay prefix
            (identifierName name)
            ++
          [ ""
          , "@" <> identifierName name
                <> "(" <> (Text.intercalate ", " . map goArgSig . Vector.toList $ procedureDocArgs d) <> ")"
                <> maybe "" ((" → " <>) . goTy) (procedureDocReturnType d)
                <> "@"
          , ""
          ]
            ++
          ( if qualifiedName /= procedureDocName d &&
               identifierName name /= procedureDocName d then
              [ "Alias for [" <> procedureDocName d <> "](#" <> prefix <> procedureDocName d <> ")"
              ]
            else
              ( if Vector.null (procedureDocArgs d) then
                  [ ]
                else
                  [ ""
                  , "==== Arguments"
                  , ""
                  ] ++
                  (concatMap goArgDesc . Vector.toList $ procedureDocArgs d) ++
                  [ "" ]
              ) ++
              [ ""
              , markdownToHaddock $ procedureDocDescription d
              ]
          )

    goItem :: Maybe Text -> Text -> (Identifier, Value Identity) -> String
    goItem namespaceMay prefix (name, DictV subitems) =
      let qualifiedName = maybe "" (<> ".") namespaceMay <> identifierName name
          heading = Text.unpack . Text.unlines $
                      [ ""
                      , "=== Module \\'" <> qualifiedName <> "\\'"
                      ]
      in
        heading ++
        unlines
          [ goItem (Just qualifiedName) prefix (Identifier k, v)
          | (StringScalar k, v) <- Map.toAscList subitems
          ]

    goItem namespaceMay prefix (name, ProcedureV (NativeProcedure _ (Just d) _)) =
      goDocumentedItem namespaceMay prefix (name, d)
    goItem namespaceMay prefix (name, ProcedureV NamespaceProcedure) =
      goDocumentedItem namespaceMay prefix (name, namespaceProcedureDoc)
    goItem namespaceMay prefix (name, FilterV (NativeFilter (Just d) _)) =
      goDocumentedItem namespaceMay prefix (name, d)
    goItem namespaceMay prefix (name, TestV (NativeTest (Just d) _)) =
      goDocumentedItem namespaceMay prefix (name, d)
    goItem namespaceMay prefix (name, _) =
      Text.unpack . Text.unlines $
        goItemHeading namespaceMay prefix (identifierName name)