module Data.Docs.ToMarkdown where
import Data.Aeson (ToJSON)
import Data.Aeson.Encode.Pretty (encodePretty', defConfig, Config(..))
import Data.Char (isAlphaNum)
import Data.Data (Proxy(..))
import Data.Docs.Docs (Documentation(..), Field(..), Docs(..))
import Data.Docs.Sample (Sample(..))
import qualified Data.List as List
import Data.Text (Text)
import qualified Data.Text as Text
import qualified Data.Text.Encoding as Text
import qualified Data.ByteString.Lazy as BSL
import Data.Monoid ((<>))
import Data.Maybe (fromMaybe)
import Safe (maximumDef, headMay)
markdown :: (ToJSON a, Docs a, Sample a) => Proxy a -> Text
markdown p =
let d = docs p
s = sample p
in section $ filter (not . List.null)
[ header (typeName d)
, desc (description d)
, fieldTable (fields d)
, valuesTable (enumeratedValues d)
, example s
]
section :: [[Text]] -> Text
section = Text.intercalate "\n\n" . map Text.unlines
desc :: Text -> [Text]
desc "" = []
desc d = [d]
header :: Text -> [Text]
header t =
[ t
, "---------------------"
]
example :: ToJSON a => a -> [Text]
example a =
[ "```"
, Text.decodeUtf8 $ BSL.toStrict $ encodePretty' conf a
, "```"
]
where
conf = defConfig { confCompare = compare }
type URL = Text
link :: Text -> URL -> Text
link t u =
"[" <> t <> "](" <> u <> ")"
anchor :: Text -> Text -> Text
anchor t n = link t $ "#" <> (Text.filter isAlphaNum $ Text.toLower n)
fieldTable :: [Field] -> [Text]
fieldTable [] = []
fieldTable fs =
table ["Field", "Type", ""] (map row fs)
where
row f =
[ fieldName f
, anchor (fieldType f) (fieldType f)
, opInfo f
]
opInfo f =
if isRequired f
then ""
else "(optional)"
valuesTable :: [Text] -> [Text]
valuesTable [] = []
valuesTable vs =
table ["Values"] (map row vs)
where
row t = [ t ]
type CellWidth = Int
cellWidth :: [Text] -> Int
cellWidth ts = maximumDef 0 (map Text.length ts)
table :: [Text] -> [[Text]] -> [Text]
table hs rs =
let jr = List.transpose $ map justifyColumn $ filter nonEmptyCol $ List.transpose (hs : rs) :: [[Text]]
hs' = fromMaybe [] $ headMay jr :: [Text]
rs' = drop 1 jr
in mconcat [ tableHeader hs', map tableRow rs' ]
where
nonEmptyCol = not . all Text.null . List.drop 1
justifyColumn :: [Text] -> [Text]
justifyColumn ts =
let w = maximumDef 0 (map Text.length ts)
in map (Text.justifyLeft w ' ') ts
tableHeader :: [Text] -> [Text]
tableHeader ts =
[tableRow ts, sepRow ts]
tableRow :: [Text] -> Text
tableRow ts =
mconcat ["| ", Text.intercalate " | " ts, " |"]
where
sepRow :: [Text] -> Text
sepRow ts =
mconcat ["|-", Text.intercalate "-|-" (map sepCell ts), "-|"]
where
sepCell t = Text.replicate (Text.length t) "-"