3 module Text.Docvim.Printer.Markdown (markdown) where
5 #if !MIN_VERSION_base(4,8,0)
6 import Control.Applicative ((<$>))
8 import Control.Monad.Reader
11 import Text.Docvim.AST
12 import Text.Docvim.Parse
13 import Text.Docvim.Visitor.Plugin
14 import Text.Docvim.Visitor.Symbol
16 data Metadata = Metadata { pluginName :: Maybe String
19 type Env = Reader Metadata String
21 data Anchor = Anchor [Attribute] String
22 data Attribute = Attribute String String
24 markdown :: Node -> String
25 markdown n = if null stripped
29 metadata = Metadata (getPluginName n) (getSymbols n)
30 stripped = rstrip (runReader (node n) metadata)
32 nodes :: [Node] -> Env
33 nodes ns = concat <$> mapM node ns
37 Blockquote b -> blockquote b >>= nl >>= nl
38 -- TODO, for readability, this should be "<br />\n" (custom, context-aware separator; see Vim.hs)
39 BreakTag -> return "<br />"
40 Code c -> return $ "`" ++ c ++ "`"
41 CommandAnnotation {} -> command n
42 CommandsAnnotation -> h2 "Commands"
44 Fenced f -> return $ fenced f ++ "\n\n"
45 FunctionAnnotation {} -> function n
46 FunctionDeclaration {} -> nodes $ functionBody n
47 FunctionsAnnotation -> h2 "Functions"
48 HeadingAnnotation h -> h2 h
49 ImageAnnotation {} -> image n
51 LinkTargets l -> return $ linkTargets l
52 List ls -> nodes ls >>= nl
53 ListItem l -> fmap ("- " ++) (nodes l) >>= nl
54 MappingAnnotation m -> mapping m
55 MappingsAnnotation -> h2 "Mappings"
56 OptionAnnotation {} -> option n
57 OptionsAnnotation -> h2 "Options"
58 Paragraph p -> nodes p >>= nl >>= nl
59 Plaintext p -> return $ sanitize p
60 -- TODO: this should be order-independent and always appear at the top.
61 -- Note that I don't really have anywhere to put the description; maybe I should
62 -- scrap it (nope: need it in the Vim help version).
63 PluginAnnotation name _ -> h1 name
65 Separator -> return $ "---" ++ "\n\n"
66 SubheadingAnnotation s -> h3 s
68 Whitespace -> return " "
71 -- | Split a string into a list of strings, each containing a single character.
72 split :: String -> [String]
74 split (s:xs) = [s]:rest where rest = split xs
76 -- | Sanitize a string for use inside markdown, escaping special HTML
78 sanitize :: String -> String
79 sanitize s = concatMap repl (split s)
87 -- | Append a newline.
89 nl = return . (++ "\n")
91 blockquote :: [Node] -> Env
93 ps' <- mapM paragraph ps
94 return $ "> " ++ intercalate "\n>\n> " ps'
96 -- Strip off trailing newlines from each paragraph.
97 paragraph p = fmap trim (node p)
98 trim contents = take (length contents - 2) contents
100 -- TODO: handle "interesting" link text like containing [, ], "
101 link :: String -> Env
104 return $ if l `elem` symbols metadata
105 -- TODO: beware names with < ` etc in them
106 -- TODO: consider not using <strong>
107 then "<strong>[`" ++ l ++ "`](" ++ gitHubAnchor l ++ ")</strong>"
108 else "<strong>`" ++ l ++ "`</strong>" -- TODO:
109 -- may want to try producing a link to Vim
110 -- online help if I can find a search for it
112 fenced :: [String] -> String
113 fenced f = "```\n" ++ code ++ "```"
114 where code = if null f
116 else intercalate "\n" f ++ "\n"
118 linkTargets :: [String] -> String
119 linkTargets ls = "<p align=\"right\">"
120 ++ unwords (map linkify $ sort ls)
125 linkify l = a $ Anchor [ Attribute "name" (sanitizeAnchor l)
126 , Attribute "href" (gitHubAnchor l)
139 heading :: Int -> String -> Env
140 heading level string = do
142 return $ replicate level '#' ++ " " ++ string ++ anch (pluginName metadata) ++ "\n\n"
144 anch name = a $ Anchor [ Attribute "name" (sanitizeAnchor $ pre ++ string)
145 , Attribute "href" (gitHubAnchor $ pre ++ string)
149 pre = maybe "" (++ "-") name
151 -- | Wraps a string in `<code>`/`</code>` tags.
152 -- TODO: remember why I'm not using backticks here.
153 codify :: String -> String
154 codify s = "<code>" ++ s ++ "</code>"
156 a :: Anchor -> String
157 a (Anchor attributes target) = "<a" ++ attrs ++ ">" ++ target ++ "</a>"
159 attrs = if not (null attributes)
160 then " " ++ attributesString attributes
163 attributesString :: [Attribute] -> String
164 attributesString as = unwords (map attributeToString as)
165 where attributeToString (Attribute name value) = name ++ "=\"" ++ value ++ "\""
167 gitHubAnchor :: String -> String
168 gitHubAnchor n = "#user-content-" ++ sanitizeAnchor n
170 option :: Node -> Env
171 option (OptionAnnotation n t d) = do
172 h <- h3 $ "`" ++ n ++ "` (" ++ t ++ ", default: " ++ def ++ ")"
173 return $ targets ++ h
174 where targets = linkTargets [n]
175 def = fromMaybe "none" d
176 option _ = invalidNode
178 command :: Node -> Env
179 command (CommandAnnotation name params) = do
180 content <- h3 $ "`:" ++ annotation ++ "`"
181 return $ target ++ content
182 where target = linkTargets [":" ++ name]
183 annotation = rstrip $ name ++ " " ++ fromMaybe "" params
184 command _ = invalidNode
186 function :: Node -> Env
187 function (FunctionAnnotation name) = do
188 content <- h3 $ "`" ++ name ++ "()`"
189 return $ target ++ content
190 where target = linkTargets [name ++ "()"]
191 function _ = invalidNode
194 image (ImageAnnotation source alignment) = do
195 return $ open ++ img ++ close
196 where open = "<p" ++ (align alignment) ++ ">\n"
197 align (Just al) = " align=\"" ++ al ++ "\""
199 img = " <img src=\"" ++ source ++ "\" />\n"
201 image _ = invalidNode
203 mapping :: String -> Env
204 mapping name = h3 $ "`" ++ name ++ "`"