implicit-0.4.0.0: programs/docgen.hs
{- ORMOLU_DISABLE -}
-- Implicit CAD. Copyright (C) 2011, Christopher Olah (chris@colah.ca)
-- Released under the GNU GPL, see LICENSE
-- FIXME: document why we need each of these.
{-# LANGUAGE ScopedTypeVariables #-}
import Prelude(IO, Show, String, Int, Maybe(Just,Nothing), Eq, return, ($), show, fmap, (<>), putStrLn, filter, zip, null, undefined, const, Bool(True,False), fst, (.), head, tail, length, (/=), (+), error, print)
import Graphics.Implicit.ExtOpenScad.Primitives (primitiveModules)
import Graphics.Implicit.ExtOpenScad.Definitions (ArgParser(AP,APFail,APExample,APTest,APTerminator,APBranch), Symbol(Symbol), OVal(ONModule), SourcePosition(SourcePosition), StateC)
import qualified Control.Exception as Ex (catch, SomeException)
import Control.Monad (forM_)
import Data.Traversable (traverse)
import Data.Text.Lazy (unpack, pack)
-- | Return true if the argument is of type ExampleDoc.
isExample :: DocPart -> Bool
isExample (ExampleDoc _ ) = True
isExample _ = False
-- | Return true if the argument is of type ArgumentDoc.
isArgument :: DocPart -> Bool
isArgument (ArgumentDoc _ _ _) = True
isArgument _ = False
-- | Return true if the argument is of type Branch.
isBranch :: DocPart -> Bool
isBranch (Branch _) = True
isBranch _ = False
dumpPrimitive :: Symbol -> [DocPart] -> Int -> IO ()
dumpPrimitive (Symbol moduleName) moduleDocList level = do
let
examples = filter isExample moduleDocList
arguments = filter isArgument moduleDocList
syntaxes = filter isBranch moduleDocList
moduleLabel = unpack moduleName
if level /= 0
then
putStrLn $ "#" <> moduleLabel
else
do
putStrLn moduleLabel
putStrLn (fmap (const '-') moduleLabel)
putStrLn ""
if null examples
then
return ()
else
do
putStrLn "#Examples:\n"
forM_ examples $ \(ExampleDoc example) ->
putStrLn $ " * `" <> example <> "`"
putStrLn ""
if null arguments
then
return ()
else
do
if level /= 0
then
putStrLn "##Arguments:\n"
else
if null syntaxes
then
putStrLn "#Arguments:\n"
else
putStrLn "#Shared Arguments:\n"
forM_ arguments $ \(ArgumentDoc (Symbol name) posfallback description) ->
case (posfallback, description) of
(Nothing, "") ->
putStrLn $ " * `" <> unpack name <> "`"
(Just fallback, "") ->
putStrLn $ " * `" <> unpack name <> " = " <> fallback <> "`"
(Nothing, _) -> do
putStrLn $ " * `" <> unpack name <> "`"
putStrLn $ " " <> description
(Just fallback, _) -> do
putStrLn $ " * `" <> unpack name <> " = " <> fallback <> "`"
putStrLn $ " " <> description
putStrLn ""
if null syntaxes
then
return ()
else
forM_ syntaxes $ \(Branch syntax) ->
dumpPrimitive (Symbol $ pack $ "Syntax " <> show (level+1)) syntax (level+1)
-- | Our entrypoint. Generate one document describing all of our primitives.
main :: IO ()
main = do
docs <- traverse (getArgParserDocs.getArgParserFrom) primitiveModules
let
names = fmap fst primitiveModules
docname = "ImplicitCAD Primitives"
putStrLn (fmap (const '=') docname)
putStrLn docname
putStrLn (fmap (const '=') docname)
putStrLn ""
putStrLn ""
forM_ (zip names docs) $ \(moduleName, moduleDocList) ->
dumpPrimitive moduleName moduleDocList 0
where
getArgParserFrom :: (Symbol, OVal) -> ArgParser(StateC [OVal])
getArgParserFrom (_, ONModule _ implementation _) = implementation sourcePosition []
where sourcePosition = SourcePosition 0 0 "docgen"
getArgParserFrom (_, _) = error "bad value in primitive array."
-- | the format we extract documentation into
data Doc = Doc String [DocPart]
deriving (Show)
data DocPart = ExampleDoc String
| ArgumentDoc Symbol (Maybe String) String
| Branch [DocPart]
| Empty
deriving (Show, Eq)
-- Here there be dragons!
-- Because we made this a Monad instead of applicative functor, there's no sane way to do this.
-- We give undefined (= an error) and let laziness prevent if from ever being touched.
-- We're using IO so that we can catch an error if this backfires.
-- If so, we *back off*.
-- | Extract Documentation from an ArgParser
getArgParserDocs ::
ArgParser a -- ^ ArgParser(s)
-> IO [DocPart] -- ^ Docs (sadly IO wrapped)
getArgParserDocs (AP name fallback doc fnext) = do
otherDocs <- Ex.catch (getArgParserDocs $ fnext undefined) (\(_ :: Ex.SomeException) -> return [])
if otherDocs /= [Empty]
then
return $ ArgumentDoc name (fmap show fallback) (unpack doc) : otherDocs
else
return [ArgumentDoc name (fmap show fallback) (unpack doc)]
getArgParserDocs (APExample str child) = do
childResults <- getArgParserDocs child
return $ ExampleDoc (unpack str):childResults
-- We try to look at as little as possible, to avoid the risk of triggering an error.
-- Yay laziness!
getArgParserDocs (APTest _ _ child) = getArgParserDocs child
-- To look at these would almost certainly be death (exception)
getArgParserDocs (APTerminator _) = return [Empty]
getArgParserDocs (APFail _) = return [Empty]
-- This one confuses me.
getArgParserDocs (APBranch children) = do
print (length children)
otherDocs <- Ex.catch (getArgParserDocs (APBranch $ tail children)) (\(_ :: Ex.SomeException) -> return [])
aResults <- getArgParserDocs $ head children
if otherDocs /= [Empty]
then
return [Branch (aResults <> otherDocs)]
else
return aResults