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