1 ---------------------------------------------------------
2 -- The main program for the hpc-markup tool, part of HPC.
3 -- Andy Gill and Colin Runciman, June 2006
4 ---------------------------------------------------------
6 module HpcMarkup (markup_plugin) where
15 import System.Directory
17 import Data.Maybe(fromJust)
21 import qualified HpcSet as Set
23 ------------------------------------------------------------------------------
25 markup_options :: FlagOptSeq
35 markup_plugin :: Plugin
36 markup_plugin = Plugin { name = "markup"
37 , usage = "[OPTION] .. <TIX_FILE> [<MODULE> [<MODULE> ..]]"
38 , options = markup_options
39 , summary = "Markup Haskell source with program coverage"
40 , implementation = markup_main
41 , init_flags = default_flags
42 , final_flags = default_final_flags
45 ------------------------------------------------------------------------------
47 markup_main :: Flags -> [String] -> IO ()
48 markup_main flags (prog:modNames) = do
50 { includeMods = Set.fromList modNames
54 { funTotals = theFunTotals
55 , altHighlight = invertOutput
59 mtix <- readTix (getTixFileName prog)
60 Tix tixs <- case mtix of
61 Nothing -> hpcError markup_plugin $ "unable to find tix file for: " ++ prog
65 sequence [ genHtmlFromMod dest_dir hpcflags1 tix theFunTotals invertOutput
67 , allowModule hpcflags1 (tixModuleName tix)
70 let index_name = "hpc_index"
71 index_fun = "hpc_index_fun"
72 index_alt = "hpc_index_alt"
73 index_exp = "hpc_index_exp"
75 let writeSummary filename cmp = do
76 let mods' = sortBy cmp mods
78 putStrLn $ "Writing: " ++ (filename ++ ".html")
80 writeFileUsing (dest_dir ++ "/" ++ filename ++ ".html") $
82 "<style type=\"text/css\">" ++
83 "table.bar { background-color: #f25913; }\n" ++
84 "td.bar { background-color: #60de51; }\n" ++
85 "td.invbar { background-color: #f25913; }\n" ++
86 "table.dashboard { border-collapse: collapse ; border: solid 1px black }\n" ++
87 ".dashboard td { border: solid 1px black }\n" ++
88 ".dashboard th { border: solid 1px black }\n" ++
90 "<table class=\"dashboard\" width=\"100%\" border=1>\n" ++
92 "<th rowspan=2><a href=\"" ++ index_name ++ ".html\">module</a></th>" ++
93 "<th colspan=3><a href=\"" ++ index_fun ++ ".html\">Top Level Definitions</a></th>" ++
94 "<th colspan=3><a href=\"" ++ index_alt ++ ".html\">Alternatives</a></th>" ++
95 "<th colspan=3><a href=\"" ++ index_exp ++ ".html\">Expressions</a></th>" ++
99 "<th colspan=2>covered / total</th>" ++
101 "<th colspan=2>covered / total</th>" ++
103 "<th colspan=2>covered / total</th>" ++
105 concat [ showModuleSummary (modName,fileName,modSummary)
106 | (modName,fileName,modSummary) <- mods'
109 showTotalSummary (mconcat
111 | (_,_,modSummary) <- mods'
113 ++ "</table></html>\n"
115 writeSummary index_name $ \ (n1,_,_) (n2,_,_) -> compare n1 n2
117 writeSummary index_fun $ \ (_,_,s1) (_,_,s2) ->
118 compare (percent (topFunTicked s2) (topFunTotal s2))
119 (percent (topFunTicked s1) (topFunTotal s1))
121 writeSummary index_alt $ \ (_,_,s1) (_,_,s2) ->
122 compare (percent (altTicked s2) (altTotal s2))
123 (percent (altTicked s1) (altTotal s1))
125 writeSummary index_exp $ \ (_,_,s1) (_,_,s2) ->
126 compare (percent (expTicked s2) (expTotal s2))
127 (percent (expTicked s1) (expTotal s1))
131 = hpcError markup_plugin $ "no .tix file or executable name specified"
139 -> IO (String, [Char], ModuleSummary)
140 genHtmlFromMod dest_dir flags tix theFunTotals invertOutput = do
141 let theHsPath = srcDirs flags
142 let modName0 = tixModuleName tix
144 (Mix origFile _ _ tabStop mix') <- readMixWithFlags flags (Right tix)
146 let arr_tix :: Array Int Integer
147 arr_tix = listArray (0,length (tixModuleTixs tix) - 1)
150 let tickedWith :: Int -> Integer
151 tickedWith n = arr_tix ! n
153 isTicked n = tickedWith n /= 0
155 let info = [ (pos,theMarkup)
156 | (gid,(pos,boxLabel)) <- zip [0 ..] mix'
157 , let binBox = case (isTicked gid,isTicked (gid+1)) of
159 (True,False) -> [TickedOnlyTrue]
160 (False,True) -> [TickedOnlyFalse]
162 , let tickBox = if isTicked gid
165 , theMarkup <- case boxLabel of
168 -> TopLevelDecl theFunTotals (tickedWith gid) : tickBox
169 LocalBox {} -> tickBox
170 BinBox _ True -> binBox
175 let modSummary = foldr (.) id
179 -> st { expTicked = ticked (expTicked st)
180 , expTotal = succ (expTotal st)
183 -> st { expTicked = ticked (expTicked st)
184 , expTotal = succ (expTotal st)
185 , altTicked = ticked (altTicked st)
186 , altTotal = succ (altTotal st)
189 st { topFunTicked = ticked (topFunTicked st)
190 , topFunTotal = succ (topFunTotal st)
193 | (gid,(_pos,boxLabel)) <- zip [0 ..] mix'
194 , let ticked = if isTicked gid
199 -- add prefix to modName argument
200 content <- readFileFromPath (hpcError markup_plugin) origFile theHsPath
202 let content' = markup tabStop info content
203 let show' = reverse . take 5 . (++ " ") . reverse . show
204 let addLine n xs = "<span class=\"lineno\">" ++ show' n ++ " </span>" ++ xs
205 let addLines = unlines . map (uncurry addLine) . zip [1 :: Int ..] . lines
206 let fileName = modName0 ++ ".hs.html"
207 putStrLn $ "Writing: " ++ fileName
208 writeFileUsing (dest_dir ++ "/" ++ fileName) $
209 unlines [ "<html><style type=\"text/css\">",
210 "span.lineno { color: white; background: #aaaaaa; border-right: solid white 12px }",
212 then "span.nottickedoff { color: #404040; background: white; font-style: oblique }"
213 else "span.nottickedoff { background: " ++ yellow ++ "}",
215 then "span.istickedoff { color: black; background: #d0c0ff; font-style: normal; }"
216 else "span.istickedoff { background: white }",
217 "span.tickonlyfalse { margin: -1px; border: 1px solid " ++ red ++ "; background: " ++ red ++ " }",
218 "span.tickonlytrue { margin: -1px; border: 1px solid " ++ green ++ "; background: " ++ green ++ " }",
219 "span.funcount { font-size: small; color: orange; z-index: 2; position: absolute; right: 20 }",
221 then "span.decl { font-weight: bold; background: #d0c0ff }"
222 else "span.decl { font-weight: bold }",
223 "span.spaces { background: white }",
225 "<pre>"] ++ addLines content' ++ "\n</pre>\n</html>\n";
227 modSummary `seq` return (modName0,fileName,modSummary)
229 data Loc = Loc !Int !Int
230 deriving (Eq,Ord,Show)
238 Bool -- display entry totals
242 markup :: Int -- ^tabStop
243 -> [(HpcPos,Markup)] -- random list of tick location pairs
244 -> String -- text to mark up
246 markup tabStop mix str = addMarkup tabStop str (Loc 1 1) [] sortedTickLocs
248 tickLocs = [ (Loc ln1 c1,Loc ln2 c2,mark)
250 , let (ln1,c1,ln2,c2) = fromHpcPos pos
252 sortedTickLocs = sortBy (\(locA1,locZ1,_) (locA2,locZ2,_) ->
253 (locA1,locZ2) `compare` (locA2,locZ1)) tickLocs
255 addMarkup :: Int -- tabStop
256 -> String -- text to mark up
257 -> Loc -- current location
258 -> [(Loc,Markup)] -- stack of open ticks, with closing location
259 -> [(Loc,Loc,Markup)] -- sorted list of tick location pairs
262 -- check the pre-condition.
263 --addMarkup tabStop cs loc os ticks
264 -- | not (isSorted (map fst os)) = error $ "addMarkup: bad closing ordering: " ++ show os
266 --addMarkup tabStop cs loc os@(_:_) ticks
267 -- | trace (show (loc,os,take 10 ticks)) False = undefined
269 -- close all open ticks, if we have reached the end
270 addMarkup _ [] _loc os [] =
271 concatMap (const closeTick) os
272 addMarkup tabStop cs loc ((o,_):os) ticks | loc > o =
273 closeTick ++ addMarkup tabStop cs loc os ticks
275 --addMarkup tabStop cs loc os ((t1,t2,tik@(TopLevelDecl {})):ticks) | loc == t1 =
276 -- openTick tik ++ closeTick ++ addMarkup tabStop cs loc os ticks
278 addMarkup tabStop cs loc os ((t1,t2,tik0):ticks) | loc == t1 =
281 | not (allowNesting tik0 tik')
282 -> addMarkup tabStop cs loc os ticks -- already marked or bool within marked bool
283 _ -> openTick tik0 ++ addMarkup tabStop cs loc (addTo (t2,tik0) os) ticks
286 addTo (t,tik) [] = [(t,tik)]
287 addTo (t,tik) ((t',tik'):xs) | t <= t' = (t,tik):(t',tik'):xs
288 | otherwise = (t',tik):(t',tik'):xs
290 addMarkup tabStop0 cs loc os ((t1,_t2,_tik):ticks) | loc > t1 =
291 -- throw away this tick, because it is from a previous place ??
292 addMarkup tabStop0 cs loc os ticks
294 addMarkup tabStop0 ('\n':cs) loc@(Loc ln col) os@((Loc ln2 col2,_):_) ticks
295 | ln == ln2 && col < col2
296 = addMarkup tabStop0 (' ':'\n':cs) loc os ticks
297 addMarkup tabStop0 (c0:cs) loc@(Loc _ p) os ticks =
298 if c0=='\n' && os/=[] then
299 concatMap (const closeTick) (downToTopLevel os) ++
300 c0 : "<span class=\"spaces\">" ++ expand 1 w ++ "</span>" ++
301 concatMap (openTick.snd) (reverse (downToTopLevel os)) ++
302 addMarkup tabStop0 cs' loc' os ticks
303 else if c0=='\t' then
304 expand p "\t" ++ addMarkup tabStop0 cs (incBy c0 loc) os ticks
306 escape c0 ++ addMarkup tabStop0 cs (incBy c0 loc) os ticks
308 (w,cs') = span (`elem` " \t") cs
309 loc' = foldl (flip incBy) loc (c0:w)
312 escape '"' = """
316 expand :: Int -> String -> String
318 expand c ('\t':s) = replicate (c' - c) ' ' ++ expand c' s
320 c' = tabStopAfter 8 c
321 expand c (' ':s) = ' ' : expand (c+1) s
322 expand _ _ = error "bad character in string for expansion"
324 incBy :: Char -> Loc -> Loc
325 incBy '\n' (Loc ln _c) = Loc (succ ln) 1
326 incBy '\t' (Loc ln c) = Loc ln (tabStopAfter tabStop0 c)
327 incBy _ (Loc ln c) = Loc ln (succ c)
329 tabStopAfter :: Int -> Int -> Int
330 tabStopAfter tabStop c = fromJust (find (>c) [1,(tabStop + 1)..])
333 addMarkup tabStop cs loc os ticks = "ERROR: " ++ show (take 10 cs,tabStop,loc,take 10 os,take 10 ticks)
335 openTick :: Markup -> String
336 openTick NotTicked = "<span class=\"nottickedoff\">"
337 openTick IsTicked = "<span class=\"istickedoff\">"
338 openTick TickedOnlyTrue = "<span class=\"tickonlytrue\">"
339 openTick TickedOnlyFalse = "<span class=\"tickonlyfalse\">"
340 openTick (TopLevelDecl False _) = openTopDecl
341 openTick (TopLevelDecl True 0)
342 = "<span class=\"funcount\">-- never entered</span>" ++
344 openTick (TopLevelDecl True 1)
345 = "<span class=\"funcount\">-- entered once</span>" ++
347 openTick (TopLevelDecl True n0)
348 = "<span class=\"funcount\">-- entered " ++ showBigNum n0 ++ " times</span>" ++ openTopDecl
349 where showBigNum n | n <= 9999 = show n
350 | otherwise = showBigNum' (n `div` 1000) ++ "," ++ showWith (n `mod` 1000)
351 showBigNum' n | n <= 999 = show n
352 | otherwise = showBigNum' (n `div` 1000) ++ "," ++ showWith (n `mod` 1000)
353 showWith n = take 3 $ reverse $ ("000" ++) $ reverse $ show n
356 closeTick = "</span>"
358 openTopDecl :: String
359 openTopDecl = "<span class=\"decl\">"
361 downToTopLevel :: [(Loc,Markup)] -> [(Loc,Markup)]
362 downToTopLevel ((_,TopLevelDecl {}):_) = []
363 downToTopLevel (o : os) = o : downToTopLevel os
364 downToTopLevel [] = []
367 -- build in logic for nesting bin boxes
369 allowNesting :: Markup -- innermost
370 -> Markup -- outermost
372 allowNesting n m | n == m = False -- no need to double nest
373 allowNesting IsTicked TickedOnlyFalse = False
374 allowNesting IsTicked TickedOnlyTrue = False
375 allowNesting _ _ = True
377 ------------------------------------------------------------------------------
379 data ModuleSummary = ModuleSummary
382 , topFunTicked :: !Int
383 , topFunTotal :: !Int
390 showModuleSummary :: (String, String, ModuleSummary) -> String
391 showModuleSummary (modName,fileName,modSummary) =
393 "<td> <tt>module <a href=\"" ++ fileName ++ "\">"
394 ++ modName ++ "</a></tt></td>\n" ++
395 showSummary (topFunTicked modSummary) (topFunTotal modSummary) ++
396 showSummary (altTicked modSummary) (altTotal modSummary) ++
397 showSummary (expTicked modSummary) (expTotal modSummary) ++
400 showTotalSummary :: ModuleSummary -> String
401 showTotalSummary modSummary =
402 "<tr style=\"background: #e0e0e0\">\n" ++
403 "<th align=left> Program Coverage Total</tt></th>\n" ++
404 showSummary (topFunTicked modSummary) (topFunTotal modSummary) ++
405 showSummary (altTicked modSummary) (altTotal modSummary) ++
406 showSummary (expTicked modSummary) (expTotal modSummary) ++
409 showSummary :: (Integral t) => t -> t -> String
410 showSummary ticked total =
411 "<td align=\"right\">" ++ showP (percent ticked total) ++ "</td>" ++
412 "<td>" ++ show ticked ++ "/" ++ show total ++ "</td>" ++
414 (case percent ticked total of
416 Just w -> bar w "bar"
419 showP Nothing = "- "
420 showP (Just x) = show x ++ "%"
421 bar 0 _ = bar 100 "invbar"
422 bar w inner = "<table cellpadding=0 cellspacing=0 width=\"100\" class=\"bar\">" ++
423 "<tr><td><table cellpadding=0 cellspacing=0 width=\"" ++ show w ++ "%\">" ++
424 "<tr><td height=12 class=" ++ show inner ++ "></td></tr>" ++
425 "</table></td></tr></table>"
427 percent :: (Integral a) => a -> a -> Maybe a
428 percent ticked total = if total == 0 then Nothing else Just (ticked * 100 `div` total)
431 instance Monoid ModuleSummary where
432 mempty = ModuleSummary
440 mappend (ModuleSummary eTik1 eTot1 tTik1 tTot1 aTik1 aTot1)
441 (ModuleSummary eTik2 eTot2 tTik2 tTot2 aTik2 aTot2)
442 = ModuleSummary (eTik1 + eTik2) (eTot1 + eTot2) (tTik1 + tTik2) (tTot1 + tTot2) (aTik1 + aTik2) (aTot1 + aTot2)
445 ------------------------------------------------------------------------------
447 writeFileUsing :: String -> String -> IO ()
448 writeFileUsing filename text = do
449 let dest_dir = reverse . dropWhile (\ x -> x /= '/') . reverse $ filename
451 -- We need to check for the dest_dir each time, because we use sub-dirs for
452 -- packages, and a single .tix file might contain information about
455 #if __GLASGOW_HASKELL__ >= 604
456 -- create the dest_dir if needed
457 when (not (null dest_dir)) $
458 createDirectoryIfMissing True dest_dir
461 writeFile filename text
463 ------------------------------------------------------------------------------
464 -- global color pallete
466 red,green,yellow :: String