Automatically generate documentation from comments in R7RS Scheme code
git clone https://git.8pit.net/scmdoc.git
1{-# LANGUAGE OverloadedStrings #-} 2 3-- | This module provides data types for building 'Formatter's. 4module SchemeDoc.Format.Types ( 5 Declaration (declId, declDesc), 6 mkDeclaration, 7 declFmt, 8 Formatable (..), 9 Formatter, 10 Section (..), 11 sectionComment, 12 defaultSection, 13 sectionFmt, 14 Component (..), 15 compAnchor, 16 compLink, 17) 18where 19 20import Control.Monad (unless) 21import Data.Char (isSpace) 22import Data.Maybe (fromMaybe) 23import qualified Data.Text as T 24import SchemeDoc.Format.Util (fromMkd) 25import SchemeDoc.Types 26 27import Text.Blaze.Html 28import qualified Text.Blaze.Html5 as H 29import qualified Text.Blaze.Html5.Attributes as A 30 31-- | Type class to convert a given type to a formatted S-expression. 32class Formatable a where 33 -- Create a 'Declaration' using the given documentation. 34 fmt :: a -> T.Text -> Declaration 35 36-- | A high-level declaration in the Scheme source code, e.g. a 37-- 'SchemeDoc.Format.Library' declaration. 38data Declaration = Declaration 39 { declId :: T.Text 40 -- ^ Internal identifier of the declaration. 41 , declDesc :: T.Text 42 -- ^ Documentation for the declaration, i.e. the 'DocComment' preceding it. 43 , declFmt' :: T.Text -> Html 44 } 45 46-- | Create a new 'Declaration' with a given identifier, description and format function. 47mkDeclaration :: T.Text -> T.Text -> (T.Text -> Html) -> Declaration 48mkDeclaration = Declaration 49 50-- | Format a declaration with an optional external identifier. 51declFmt :: Declaration -> Maybe T.Text -> Html 52declFmt d newName = declFmt' d $ fromMaybe (declId d) newName 53 54-- | Function for converting an S-expression into a 'Declaration' 55-- based on the given documentation for the S-expression. 56type Formatter = Sexp -> T.Text -> Maybe Declaration 57 58------------------------------------------------------------------------ 59 60-- | Section represents a section comment in the source. 61data Section 62 = Section 63 T.Text 64 -- ^ Section title. 65 T.Text 66 -- ^ Sectiond description. 67 68-- Character used to identify section comments. 69sectionChar :: Char 70sectionChar = '|' 71 72-- Remove all leading ASCII space characters. 73ltrim :: T.Text -> T.Text 74ltrim = T.dropWhile isSpace 75 76-- Remove all trailing ASCII space characters. 77rtrim :: T.Text -> T.Text 78rtrim = T.dropWhileEnd isSpace 79 80-- Remove all trailing and leading ASCII space characters. 81trim :: T.Text -> T.Text 82trim = rtrim . ltrim 83 84-- | If the given comment text constitutes a 'Section' title 85-- return the title, otherwise return 'Nothing'. 86sectionComment :: T.Text -> Maybe T.Text 87sectionComment t = 88 if T.length t >= 1 && T.head t == sectionChar 89 then Just (ltrim $ T.tail t) 90 else Nothing 91 92-- | Default section, used if the input file doesn't contain a single 93-- section comment of its own. 94defaultSection :: Section 95defaultSection = Section "Declarations" "" 96 97-- | Format a section comment as 'Html'. 98sectionFmt :: Section -> Html 99sectionFmt s@(Section n desc) = do100 -- TODO: Add <section> tags for each H2101 H.h2 ! A.id (textValue $ compAnchor (S s)) $102 toHtml n103 unless (T.null desc) $ do104 H.p $ fromMkd desc105106------------------------------------------------------------------------107108-- | Component in the documented source code.109-- This is either a 'Declaration' or a 'Section' comment.110data Component = D Declaration | S Section111112-- | Unique anchor for a compoment.113compAnchor :: Component -> T.Text114compAnchor (D c) = declId c -- TODO: Ensure that this is aligned with format function115compAnchor (S (Section n _)) = "section-" `T.append` toAnchor n116 where117 toAnchor :: T.Text -> T.Text118 toAnchor = T.map (\c -> if isSpace c then '-' else c) . T.toLower . trim119120-- | Generate an anchor tag which links to a given 'Component'.121compLink :: Component -> Html122compLink c = do123 H.a ! A.href (textValue $ T.cons '#' (compAnchor c)) $124 toHtml (compName c)125 where126 compName :: Component -> T.Text127 compName (D c') = declId c'128 compName (S (Section n _)) = n