]> git.wincent.com - docvim.git/blob - lib/Text/Docvim/Printer/Markdown.hs
Apply suggested lint fixes
[docvim.git] / lib / Text / Docvim / Printer / Markdown.hs
1 {-# LANGUAGE CPP #-}
2
3 module Text.Docvim.Printer.Markdown (markdown) where
4
5 #if !MIN_VERSION_base(4,8,0)
6 import Control.Applicative ((<$>))
7 #endif
8 import Control.Monad.Reader
9 import Data.List
10 import Data.Maybe
11 import Text.Docvim.AST
12 import Text.Docvim.Parse
13 import Text.Docvim.Visitor.Plugin
14 import Text.Docvim.Visitor.Symbol
15
16 data Metadata = Metadata { pluginName :: Maybe String
17                          , symbols :: [String]
18                          }
19 type Env = Reader Metadata String
20
21 data Anchor = Anchor [Attribute] String
22 data Attribute = Attribute String String
23
24 markdown :: Node -> String
25 markdown n = if null stripped
26              then ""
27              else stripped ++ "\n"
28   where
29     metadata = Metadata (getPluginName n) (getSymbols n)
30     stripped = rstrip (runReader (node n) metadata)
31
32 nodes :: [Node] -> Env
33 nodes ns = concat <$> mapM node ns
34
35 node :: Node -> Env
36 node n = case n of
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"
43   DocBlock d              -> nodes d
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   Link l                  -> link l
50   LinkTargets l           -> return $ linkTargets l
51   List ls                 -> nodes ls >>= nl
52   ListItem l              -> fmap ("- " ++) (nodes l) >>= nl
53   MappingAnnotation m     -> mapping m
54   MappingsAnnotation      -> h2 "Mappings"
55   OptionAnnotation {}     -> option n
56   OptionsAnnotation       -> h2 "Options"
57   Paragraph p             -> nodes p >>= nl >>= nl
58   Plaintext p             -> return $ sanitize p
59   -- TODO: this should be order-independent and always appear at the top.
60   -- Note that I don't really have anywhere to put the description; maybe I should
61   -- scrap it (nope: need it in the Vim help version).
62   PluginAnnotation name _ -> h1 name
63   Project p               -> nodes p
64   Separator               -> return $ "---" ++ "\n\n"
65   SubheadingAnnotation s  -> h3 s
66   Unit u                  -> nodes u
67   Whitespace              -> return " "
68   _                       -> return ""
69
70 -- | Split a string into a list of strings, each containing a single character.
71 split :: String -> [String]
72 split [] = []
73 split (s:xs) = [s]:rest where rest = split xs
74
75 -- | Sanitize a string for use inside markdown, escaping special HTML
76 -- characters.
77 sanitize :: String -> String
78 sanitize s = concatMap repl (split s)
79   where
80     repl "<" = "&lt;"
81     repl ">" = "&gt;"
82     repl "&" = "&amp;"
83     repl "\"" = "&quot;"
84     repl other = other
85
86 -- | Append a newline.
87 nl :: String -> Env
88 nl = return . (++ "\n")
89
90 blockquote :: [Node] -> Env
91 blockquote ps = do
92   ps' <- mapM paragraph ps
93   return $ "> " ++ intercalate "\n>\n> " ps'
94   where
95     -- Strip off trailing newlines from each paragraph.
96     paragraph p = fmap trim (node p)
97     trim contents = take (length contents - 2) contents
98
99 -- TODO: handle "interesting" link text like containing [, ], "
100 link :: String -> Env
101 link l = do
102   metadata <- ask
103   return $ if l `elem` symbols metadata
104            -- TODO: beware names with < ` etc in them
105            -- TODO: consider not using <strong>
106            then "<strong>[`" ++ l ++ "`](" ++ gitHubAnchor l ++ ")</strong>"
107            else "<strong>`" ++ l ++ "`</strong>" -- TODO:
108                                 -- may want to try producing a link to Vim
109                                 -- online help if I can find a search for it
110
111 fenced :: [String] -> String
112 fenced f = "```\n" ++ code ++ "```"
113   where code = if null f
114                then ""
115                else intercalate "\n" f ++ "\n"
116
117 linkTargets :: [String] -> String
118 linkTargets ls =  "<p align=\"right\">"
119                ++ unwords (map linkify $ sort ls)
120                ++ "</p>"
121                ++ "\n"
122   where
123     linkify l = a $ Anchor [ Attribute "name" (sanitizeAnchor l)
124                            , Attribute "href" (gitHubAnchor l)
125                            ]
126                            (codify l)
127
128 h1 :: String -> Env
129 h1 = heading 1
130
131 h2 :: String -> Env
132 h2 = heading 2
133
134 h3 :: String -> Env
135 h3 = heading 3
136
137 heading :: Int -> String -> Env
138 heading level string = do
139   metadata <- ask
140   return $ "\n" ++ replicate level '#' ++ " " ++ string ++ anch (pluginName metadata) ++ "\n\n"
141   where
142     anch name = a $ Anchor [ Attribute "name" (sanitizeAnchor $ pre ++ string)
143                            , Attribute "href" (gitHubAnchor $ pre ++ string)
144                            ]
145                            ""
146       where
147         pre = maybe "" (++ "-") name
148
149 -- | Wraps a string in `<code>`/`</code>` tags.
150 -- TODO: remember why I'm not using backticks here.
151 codify :: String -> String
152 codify s = "<code>" ++ s ++ "</code>"
153
154 a :: Anchor -> String
155 a (Anchor attributes target) = "<a" ++ attrs ++ ">" ++ target ++ "</a>"
156   where
157     attrs = if not (null attributes)
158             then " " ++ attributesString attributes
159             else ""
160
161 attributesString :: [Attribute] -> String
162 attributesString as = unwords (map attributeToString as)
163   where attributeToString (Attribute name value) = name ++ "=\"" ++ value ++ "\""
164
165 gitHubAnchor :: String -> String
166 gitHubAnchor n = "#user-content-" ++ sanitizeAnchor n
167
168 -- TODO: make sure symbol table knows about option targets too
169 option :: Node -> Env
170 option (OptionAnnotation n t d) = do
171   h <- h3 $ "`" ++ n ++ "` (" ++ t ++ ", default: " ++ def ++ ")"
172   return $ targets ++ h
173   where targets = linkTargets [n]
174         def = fromMaybe "none" d
175 option _ = invalidNode
176
177 command :: Node -> Env
178 command (CommandAnnotation name params) = do
179   content <- h3 $ "`:" ++ annotation ++ "`"
180   return $ target ++ content
181   where target = linkTargets [":" ++ name]
182         annotation = rstrip $ name ++ " " ++ fromMaybe "" params
183 command _ = invalidNode
184
185 function :: Node -> Env
186 function (FunctionAnnotation name) = do
187   content <- h3 $ "`" ++ name ++ "()`"
188   return $ target ++ content
189   where target = linkTargets [name ++ "()"]
190 function _ = invalidNode
191
192 mapping :: String -> Env
193 mapping name = h3 $ "`" ++ name ++ "`"