{- Code generator for function documentation tables (Function / Return Type / Description / Example / Result). Usage: runghc doc-to-sgml.hs < documentation > out -} import Control.Applicative import Data.Char import Data.List import Data.Maybe trim :: String -> String trim = f . f where f = reverse . dropWhile isSpace list_find :: (Eq a) => [a] -> [a] -> Maybe Int list_find [] _ = error "list_find: Empty search string" list_find needle@(x:xs) haystack = f 0 haystack where len = length needle - 1 f i [] = Nothing f i (y:ys) = if x == y && (xs == take len ys) then Just i else f (i+1) ys explode :: (Eq a) => [a] -> [a] -> [[a]] explode delim str = f str where len = length delim f str = fromMaybe [str] $ do pos <- list_find delim str let (a,b) = splitAt pos str return (a : f (drop len b)) data Line = Line Int Int String deriving (Show) subLine :: Line -> String -> Line subLine (Line number indent text) newtext = Line number indent newtext line_no :: Line -> Int line_no (Line n _ _) = n line_indent :: Line -> Int line_indent (Line _ n _) = n line_str :: Line -> String line_str (Line _ _ str) = str line_error :: String -> Line -> a line_error info line = error (info ++ ": error at line " ++ show (line_no line)) line_split :: Line -> [String] -> [String] line_split line splitters = f (line_str line) splitters where f str [] = [str] f str (x:xs) = a : f (drop (length x) b) xs where (a,b) = splitAt (fromMaybe err $ list_find x str) str err = line_error ("line_split " ++ show (line_str line) ++ " " ++ show splitters) line lines' :: String -> [Line] lines' str = filter noblank $ zipWith mkLine [1..] (lines str) where noblank line = trim (line_str line) /= "" mkLine num line = Line num (indentOf line) (trim line) indentOf line = length $ takeWhile (== '\t') line data DocEntry = DocEntry { function :: String, returns :: Type, description :: String, examples :: [(String, Result)] } deriving (Show) data Type = Type String | Enum String [String] deriving (Show) data Result = Result String | ResultSet [String] deriving (Show) entriesToSgml :: String -> String -> [DocEntry] -> String entriesToSgml table_id title entries = " \n" ++ " " ++ title ++ "\n" ++ "\n" ++ " \n" ++ " \n" ++ " \n" ++ " Function\n" ++ " Return Type\n" ++ " Description\n" ++ " Example\n" ++ " Result\n" ++ " \n" ++ " \n" ++ "\n" ++ " \n" ++ concatMap entryToSgml entries ++ " \n" ++ " \n" ++ "
" typeToSgml :: Type -> String typeToSgml (Type t) = "" ++ t ++ "" typeToSgml (Enum t xs) = "" ++ t ++ "" ++ " -  one of:\n" ++ "\n" ++ unlines xs ++ "\n" ++ entryIndent "" resultToSgml :: Result -> String resultToSgml (Result str) = "" ++ str ++ "" resultToSgml (ResultSet strs) = "\n\n" ++ (unlines . map (" " ++)) strs ++ "(" ++ show len ++ (if len == 1 then " row" else " rows") ++ ")\n" ++ "\n" ++ entryIndent "" where len = length strs rowIndent :: String -> String rowIndent x = " " ++ x entryIndent :: String -> String entryIndent x = " " ++ x exampleToSgml :: Maybe (String, Result) -> String exampleToSgml (Just (example, result)) = entryIndent "" ++ example ++ "\n" ++ entryIndent "" ++ resultToSgml result ++ "\n" exampleToSgml Nothing = entryIndent "\n" ++ entryIndent "\n" examplesToSgml :: [(String, Result)] -> String examplesToSgml xs = do x <- xs rowIndent "\n" ++ (exampleToSgml (Just x)) ++ rowIndent "\n" entryToSgml :: DocEntry -> String entryToSgml ent = rowIndent "\n" ++ entry ("" ++ function ent ++ "") ++ entry (typeToSgml (returns ent)) ++ entry (description ent) ++ exampleToSgml (if exlen > 0 then Just ex else Nothing) ++ rowIndent "\n" ++ (if exlen > 1 then examplesToSgml exs else "") where (ex:exs) = examples ent exlen = length (examples ent) entry x = entryIndent (if exlen > 1 then "" else "") ++ x ++ "\n" parseExamples :: [Line] -> [(String, Result)] parseExamples [] = [] parseExamples (x:xs) = if line_indent x /= 1 then line_error "parseExamples" x else if result == "" then (example, ResultSet (map line_str set)) : parseExamples after_set else (example, Result result) : parseExamples xs where [example, result] = map trim $ line_split x ["=>"] [set, after_set] = [takeWhile f xs, dropWhile f xs] f = (>= 2) . line_indent parseType :: Line -> Type parseType line = if elem '{' str then if c /= "" then line_error "parseType" line else Enum a (map trim $ explode "|" b) else Type str where str = line_str line [a,b,c] = map trim $ line_split line ["{", "}"] parseDocEntry :: [Line] -> (DocEntry, [Line]) parseDocEntry (x:xs) = if line_indent x /= 0 then line_error "parseDocEntry" x else (DocEntry { function = a, returns = parseType (subLine x b), description = c, examples = parseExamples xlines }, rest) where [a,b,c] = map trim $ line_split x ["->", ":"] f = (>= 1) . line_indent xlines = takeWhile f xs rest = dropWhile f xs parseDocEntries :: String -> [DocEntry] parseDocEntries str = f (lines' str) where f [] = [] f lines = docEntry : f rest where (docEntry, rest) = parseDocEntry lines main = do entries <- parseDocEntries <$> getContents --putStr $ unlines $ map show entries putStr $ entriesToSgml "json-func-table" "json Functions" entries