Adding hpc tools, as a single program.
[ghc-hetmet.git] / utils / hpc / HpcMarkup.hs
1 ---------------------------------------------------------
2 -- The main program for the hpc-markup tool, part of HPC.
3 -- Andy Gill and Colin Runciman, June 2006
4 ---------------------------------------------------------
5
6 module HpcMarkup (markup_plugin) where
7
8 import Trace.Hpc.Mix
9 import Trace.Hpc.Tix
10 import Trace.Hpc.Util
11
12 import HpcFlags
13
14 import System.Environment
15 import Data.List
16 import Data.Maybe(fromJust)
17 import Data.Array
18 import qualified Data.Set as Set
19
20 ------------------------------------------------------------------------------
21
22 markup_options = 
23   [ excludeOpt,includeOpt,hpcDirOpt,hsDirOpt,funTotalsOpt
24   , altHighlightOpt
25   , destDirOpt
26   ]
27          
28 markup_plugin = Plugin { name = "markup"
29                        , usage = "[OPTION] .. <TIX_FILE> [<MODULE> [<MODULE> ..]]" 
30                        , options = markup_options 
31                        , summary = "Markup Haskell source with program coverage"
32                        , implementation = markup_main
33                        , init_flags = default_flags
34                        , final_flags = default_final_flags
35                        }
36
37 ------------------------------------------------------------------------------
38
39 markup_main :: Flags -> [String] -> IO ()
40 markup_main flags (prog:modNames) = do
41   let hpcflags1 = flags 
42                 { includeMods = Set.fromList modNames
43                                    `Set.union` 
44                                 includeMods flags }
45   let Flags
46        { hpcDirs = hpcDirs
47        , hsDirs = theHsPath
48        , funTotals = theFunTotals
49        , altHighlight = invertOutput
50        , destDir = dest_dir
51        }  = hpcflags1
52
53   mtix <- readTix (getTixFileName prog)
54   Tix tixs <- case mtix of
55     Nothing -> error $ "unable to find tix file for: " ++ prog
56     Just a -> return a
57
58   mods <-
59      sequence [ genHtmlFromMod dest_dir hpcDirs tix theFunTotals theHsPath invertOutput
60               | tix <- tixs
61               , allowModule hpcflags1 (tixModuleName tix)
62               ]
63
64   let index_name = "hpc_index"
65       index_fun  = "hpc_index_fun"
66       index_alt  = "hpc_index_alt"
67       index_exp  = "hpc_index_exp"
68
69   let writeSummary name cmp = do
70         let mods' = sortBy cmp mods
71    
72         putStrLn $ "Writing: " ++ (name ++ ".html")
73         writeFile (dest_dir ++ "/" ++ name ++ ".html") $ 
74             "<html>" ++
75             "<style type=\"text/css\">" ++
76             "table.bar { background-color: #f25913; }\n" ++
77             "td.bar { background-color: #60de51;  }\n" ++
78             "table.dashboard { border-collapse: collapse  ; border: solid 1px black }\n" ++
79             ".dashboard td { border: solid 1px black }\n" ++
80             ".dashboard th { border: solid 1px black }\n" ++
81             "</style>\n" ++
82             "<table class=\"dashboard\" width=\"100%\" border=1>\n" ++
83             "<tr>" ++
84             "<th rowspan=2><a href=\"" ++ index_name ++ ".html\">module</a></th>" ++
85             "<th colspan=3><a href=\"" ++ index_fun ++ ".html\">Top Level Definitions</a></th>" ++
86             "<th colspan=3><a href=\"" ++ index_alt ++ ".html\">Alternatives</a></th>" ++
87             "<th colspan=3><a href=\"" ++ index_exp ++ ".html\">Expressions</a></th>" ++
88             "</tr>" ++
89             "<tr>" ++
90             "<th>%</th>" ++
91             "<th colspan=2>covered / total</th>" ++
92             "<th>%</th>" ++
93             "<th colspan=2>covered / total</th>" ++
94             "<th>%</th>" ++
95             "<th colspan=2>covered / total</th>" ++
96             "</tr>" ++
97             concat [ showModuleSummary (modName,fileName,summary)
98                    | (modName,fileName,summary) <- mods'
99                    ] ++
100             "<tr></tr>" ++
101             showTotalSummary (foldr1 combineSummary 
102                                  [ summary 
103                                  | (_,_,summary) <- mods'
104                                  ])
105                    ++ "</table></html>\n"
106
107   writeSummary index_name  $ \ (n1,_,_) (n2,_,_) -> compare n1 n2
108   
109   writeSummary index_fun $ \ (_,_,s1) (_,_,s2) -> 
110         compare (percent (topFunTicked s2) (topFunTotal s2))
111                 (percent (topFunTicked s1) (topFunTotal s1))
112
113   writeSummary index_alt $ \ (_,_,s1) (_,_,s2) -> 
114         compare (percent (altTicked s2) (altTotal s2))
115                 (percent (altTicked s1) (altTotal s1))
116
117   writeSummary index_exp $ \ (_,_,s1) (_,_,s2) -> 
118         compare (percent (expTicked s2) (expTotal s2))
119                 (percent (expTicked s1) (expTotal s1))
120
121
122 markup_main flags [] = error $ "no .tix file or executable name specified" 
123
124 genHtmlFromMod
125   :: String
126   -> [FilePath]
127   -> TixModule
128   -> Bool
129   -> [String]
130   -> Bool
131   -> IO (String, [Char], ModuleSummary)
132 genHtmlFromMod dest_dir hpcDirs tix theFunTotals theHsPath invertOutput = do
133   let modName0 = tixModuleName tix 
134
135   (Mix origFile _ mixHash tabStop mix') <- readMix hpcDirs modName0
136
137   let arr_tix :: Array Int Integer
138       arr_tix = listArray (0,length (tixModuleTixs tix) - 1)
139               $ tixModuleTixs tix
140
141   let tickedWith :: Int -> Integer
142       tickedWith n = arr_tix ! n 
143
144       isTicked n = tickedWith n /= 0
145
146   let info = [ (pos,theMarkup)
147              | (gid,(pos,boxLabel)) <- zip [0 ..] mix'
148              , let binBox = case (isTicked gid,isTicked (gid+1)) of
149                                (False,False) -> [] 
150                                (True,False)  -> [TickedOnlyTrue]
151                                (False,True)  -> [TickedOnlyFalse]
152                                (True,True)   -> []
153              , let tickBox = if isTicked gid
154                              then [IsTicked]
155                              else [NotTicked]
156              , theMarkup <- case boxLabel of
157                                   ExpBox {} -> tickBox
158                                   TopLevelBox {} 
159                                             -> TopLevelDecl theFunTotals (tickedWith gid) : tickBox
160                                   LocalBox {}   -> tickBox
161                                   BinBox _ True -> binBox
162                                   _             -> []
163              ]
164
165
166   let summary = foldr (.) id 
167              [ \ st -> 
168                case boxLabel of
169                  ExpBox False
170                         -> st { expTicked = ticked (expTicked st)
171                               , expTotal = succ (expTotal st)
172                               }
173                  ExpBox True
174                         -> st { expTicked = ticked (expTicked st)
175                               , expTotal = succ (expTotal st)
176                               , altTicked = ticked (altTicked st)
177                               , altTotal = succ (altTotal st)
178                               }
179                  TopLevelBox _ -> 
180                            st { topFunTicked = ticked (topFunTicked st)
181                               , topFunTotal = succ (topFunTotal st)
182                               }
183                  _ -> st
184              | (gid,(_pos,boxLabel)) <- zip [0 ..] mix'
185              , let ticked = if isTicked gid
186                             then succ
187                             else id
188              ] $ ModuleSummary 
189                   { expTicked = 0
190                   , expTotal  = 0
191                   , topFunTicked = 0
192                   , topFunTotal  = 0
193                   , altTicked = 0
194                   , altTotal  = 0
195                   }
196
197   -- add prefix to modName argument
198   content <- readFileFromPath origFile theHsPath
199
200   let content' = markup tabStop info content
201   let show' = reverse . take 5 . (++ "       ") . reverse . show
202   let addLine n xs = "<span class=\"lineno\">" ++ show' n ++ " </span>" ++ xs 
203   let addLines = unlines . map (uncurry addLine) . zip [1 :: Int ..] . lines
204   let fileName = modName0 ++ ".hs.html"
205   putStrLn $ "Writing: " ++ fileName
206   writeFile (dest_dir ++ "/" ++ fileName) $
207             unlines [ "<html><style type=\"text/css\">",
208                      "span.lineno { color: white; background: #aaaaaa; border-right: solid white 12px }",
209                      if invertOutput
210                      then "span.nottickedoff { color: #404040; background: white; font-style: oblique }"
211                      else "span.nottickedoff { background: " ++ yellow ++ "}",
212                      if invertOutput
213                      then "span.istickedoff { color: black; background: #d0c0ff; font-style: normal; }"
214                      else "span.istickedoff { background: white }",
215                      "span.tickonlyfalse { margin: -1px; border: 1px solid " ++ red ++ "; background: " ++ red ++ " }",
216                      "span.tickonlytrue  { margin: -1px; border: 1px solid " ++ green ++ "; background: " ++ green ++ " }",
217                      "span.funcount { font-size: small; color: orange; z-index: 2; position: absolute; right: 20 }",
218                      if invertOutput
219                      then "span.decl { font-weight: bold; background: #d0c0ff }"
220                      else "span.decl { font-weight: bold }",
221                      "span.spaces    { background: white }",
222                      "</style>",
223                      "<pre>"] ++ addLines content' ++ "\n</pre>\n</html>\n";
224
225   summary `seq` return (modName0,fileName,summary)
226
227 data Loc = Loc !Int !Int
228          deriving (Eq,Ord,Show)
229
230 data Markup 
231      = NotTicked 
232      | TickedOnlyTrue 
233      | TickedOnlyFalse 
234      | IsTicked
235      | TopLevelDecl 
236            Bool     -- display entry totals
237            Integer 
238      deriving (Eq,Show)
239
240 markup    :: Int                -- ^tabStop
241           -> [(HpcPos,Markup)]  -- random list of tick location pairs
242           -> String             -- text to mark up
243           -> String
244 markup tabStop mix str = addMarkup tabStop str (Loc 1 1) [] sortedTickLocs 
245   where
246     tickLocs = [ (Loc ln1 c1,Loc ln2 c2,mark)
247                | (pos,mark) <- mix
248                , let (ln1,c1,ln2,c2) = fromHpcPos pos
249                ]
250     sortedTickLocs = sortBy (\(locA1,locZ1,_) (locA2,locZ2,_) ->
251                               (locA1,locZ2) `compare` (locA2,locZ1)) tickLocs
252
253 addMarkup :: Int                -- tabStop
254           -> String             -- text to mark up
255           -> Loc                -- current location
256           -> [(Loc,Markup)]     -- stack of open ticks, with closing location
257           -> [(Loc,Loc,Markup)] -- sorted list of tick location pairs
258           -> String
259
260 -- check the pre-condition.
261 --addMarkup tabStop cs loc os ticks 
262 --   | not (isSorted (map fst os)) = error $ "addMarkup: bad closing ordering: " ++ show os
263
264 --addMarkup tabStop cs loc os@(_:_) ticks 
265 --   | trace (show (loc,os,take 10 ticks)) False = undefined
266
267 -- close all open ticks, if we have reached the end
268 addMarkup _ [] _loc os [] =
269   concatMap (const closeTick) os 
270 addMarkup tabStop cs loc ((o,_):os) ticks | loc > o =
271   closeTick ++ addMarkup tabStop cs loc os ticks
272
273 --addMarkup tabStop cs loc os ((t1,t2,tik@(TopLevelDecl {})):ticks) | loc == t1 =
274 --   openTick tik ++ closeTick ++ addMarkup tabStop cs loc os ticks
275
276 addMarkup tabStop cs loc os ((t1,t2,tik0):ticks) | loc == t1 =
277   case os of
278   ((_,tik'):_) 
279     | not (allowNesting tik0 tik') 
280     -> addMarkup tabStop cs loc os ticks -- already marked or bool within marked bool
281   _ -> openTick tik0 ++ addMarkup tabStop cs loc (addTo (t2,tik0) os) ticks
282   where
283
284   addTo (t,tik) []             = [(t,tik)]
285   addTo (t,tik) ((t',tik'):xs) | t <= t' = (t,tik):(t',tik'):xs
286                                | t > t'  = (t',tik):(t',tik'):xs 
287
288 addMarkup tabStop0 cs loc os ((t1,_t2,_tik):ticks) | loc > t1 =
289           -- throw away this tick, because it is from a previous place ??
290           addMarkup tabStop0 cs loc os ticks
291
292 addMarkup tabStop0 ('\n':cs) loc@(Loc ln col) os@((Loc ln2 col2,_):_) ticks 
293           | ln == ln2 && col < col2
294           = addMarkup tabStop0 (' ':'\n':cs) loc os ticks 
295 addMarkup tabStop0 (c0:cs) loc@(Loc _ p) os ticks =
296   if c0=='\n' && os/=[] then
297     concatMap (const closeTick) (downToTopLevel os) ++
298     c0 : "<span class=\"spaces\">" ++ expand 1 w ++ "</span>" ++
299     concatMap (openTick.snd) (reverse (downToTopLevel os)) ++
300     addMarkup tabStop0 cs' loc' os ticks
301   else if c0=='\t' then
302     expand p "\t" ++ addMarkup tabStop0 cs (incBy c0 loc) os ticks
303   else
304     escape c0 ++ addMarkup tabStop0 cs (incBy c0 loc) os ticks
305   where
306   (w,cs') = span (`elem` " \t") cs
307   loc' = foldl (flip incBy) loc (c0:w)
308   escape '>' = "&gt;"
309   escape '<' = "&lt;"
310   escape '"' = "&quot;"
311   escape '&' = "&amp;"
312   escape c  = [c]
313
314   expand :: Int -> String -> String
315   expand _ ""       = "" 
316   expand c ('\t':s) = replicate (c' - c) ' ' ++ expand c' s
317     where
318     c' = tabStopAfter 8 c
319   expand c (' ':s)  = ' ' : expand (c+1) s
320   expand _ _        = error "bad character in string for expansion"
321   
322   incBy :: Char -> Loc -> Loc
323   incBy '\n' (Loc ln _c) = Loc (succ ln) 1
324   incBy '\t' (Loc ln c) = Loc ln (tabStopAfter tabStop0 c)
325   incBy _    (Loc ln c) = Loc ln (succ c)
326   
327   tabStopAfter :: Int -> Int -> Int
328   tabStopAfter tabStop c = fromJust (find (>c) [1,(tabStop + 1)..])
329
330   
331 addMarkup tabStop cs loc os ticks = "ERROR: " ++ show (take 10 cs,tabStop,loc,take 10 os,take 10 ticks)
332
333 openTick :: Markup -> String
334 openTick NotTicked       = "<span class=\"nottickedoff\">" 
335 openTick IsTicked       = "<span class=\"istickedoff\">" 
336 openTick TickedOnlyTrue  = "<span class=\"tickonlytrue\">" 
337 openTick TickedOnlyFalse = "<span class=\"tickonlyfalse\">" 
338 openTick (TopLevelDecl False _) = openTopDecl
339 openTick (TopLevelDecl True 0) 
340          = "<span class=\"funcount\">-- never entered</span>" ++
341            openTopDecl
342 openTick (TopLevelDecl True 1) 
343          = "<span class=\"funcount\">-- entered once</span>" ++
344            openTopDecl
345 openTick (TopLevelDecl True n0) 
346          = "<span class=\"funcount\">-- entered " ++ showBigNum n0 ++ " times</span>" ++ openTopDecl
347   where showBigNum n | n <= 9999 = show n
348                      | otherwise = showBigNum' (n `div` 1000) ++ "," ++ showWith (n `mod` 1000)
349         showBigNum' n | n <= 999 = show n
350                       | otherwise = showBigNum' (n `div` 1000) ++ "," ++ showWith (n `mod` 1000)
351         showWith n = take 3 $ reverse $ ("000" ++) $ reverse $ show n
352
353 closeTick :: String
354 closeTick = "</span>"
355
356 openTopDecl :: String
357 openTopDecl = "<span class=\"decl\">"
358
359 downToTopLevel :: [(Loc,Markup)] -> [(Loc,Markup)]
360 downToTopLevel ((_,TopLevelDecl {}):_) = []
361 downToTopLevel (o : os)               = o : downToTopLevel os
362 downToTopLevel []                     = []
363
364
365 -- build in logic for nesting bin boxes
366
367 allowNesting :: Markup  -- innermost
368             -> Markup   -- outermost
369             -> Bool
370 allowNesting n m               | n == m = False -- no need to double nest
371 allowNesting IsTicked TickedOnlyFalse   = False
372 allowNesting IsTicked TickedOnlyTrue    = False
373 allowNesting _ _                        = True
374
375 ------------------------------------------------------------------------------
376
377 data ModuleSummary = ModuleSummary 
378      { expTicked :: !Int
379      , expTotal  :: !Int
380      , topFunTicked :: !Int
381      , topFunTotal  :: !Int
382      , altTicked :: !Int
383      , altTotal  :: !Int
384      }
385      deriving (Show)
386
387
388 showModuleSummary :: (String, String, ModuleSummary) -> String
389 showModuleSummary (modName,fileName,summary) =
390   "<tr>\n" ++ 
391   "<td>&nbsp;&nbsp;<tt>module <a href=\"" ++ fileName ++ "\">" 
392                               ++ modName ++ "</a></tt></td>\n" ++
393    showSummary (topFunTicked summary) (topFunTotal summary) ++
394    showSummary (altTicked summary) (altTotal summary) ++
395    showSummary (expTicked summary) (expTotal summary) ++
396   "</tr>\n"
397
398 showTotalSummary :: ModuleSummary -> String
399 showTotalSummary summary =
400   "<tr style=\"background: #e0e0e0\">\n" ++ 
401   "<th align=left>&nbsp;&nbsp;Program Coverage Total</tt></th>\n" ++
402    showSummary (topFunTicked summary) (topFunTotal summary) ++
403    showSummary (altTicked summary) (altTotal summary) ++
404    showSummary (expTicked summary) (expTotal summary) ++
405   "</tr>\n"
406
407 showSummary :: (Integral t) => t -> t -> String
408 showSummary ticked total = 
409                 "<td align=\"right\">" ++ showP (percent ticked total) ++ "</td>" ++
410                 "<td>" ++ show ticked ++ "/" ++ show total ++ "</td>" ++
411                 "<td width=100>" ++ 
412                     (case percent ticked total of
413                        Nothing -> "&nbsp;"
414                        Just w -> "<table cellpadding=0 cellspacing=0 width=\"100\" class=\"bar\">" ++
415                                      "<tr><td><table cellpadding=0 cellspacing=0 width=\"" ++ show w ++ "%\">" ++
416                               "<tr><td height=12 class=\"bar\"></td></tr>" ++
417                               "</table></td></tr></table>")
418                               ++ "</td>"
419      where
420         showP Nothing = "-&nbsp;"
421         showP (Just x) = show x ++ "%"
422
423 percent :: (Integral a) => a -> a -> Maybe a
424 percent ticked total = if total == 0 then Nothing else Just (ticked * 100 `div` total)
425
426
427 combineSummary :: ModuleSummary -> ModuleSummary -> ModuleSummary
428 combineSummary (ModuleSummary eTik1 eTot1 tTik1 tTot1 aTik1 aTot1)
429                (ModuleSummary eTik2 eTot2 tTik2 tTot2 aTik2 aTot2)
430   = ModuleSummary (eTik1 + eTik2) (eTot1 + eTot2) (tTik1 + tTik2) (tTot1 + tTot2) (aTik1 + aTik2) (aTot1 + aTot2)
431
432 ------------------------------------------------------------------------------
433 -- global color pallete
434
435 red,green,yellow :: String
436 red    = "#f20913"
437 green  = "#60de51"
438 yellow = "yellow"
439
440 ------------------------------------------------------------------------------
441
442 readFileFromPath :: String -> [String] -> IO String
443 readFileFromPath filename@('/':_) _ = readFile filename
444 readFileFromPath filename path0 = readTheFile path0
445   where
446         readTheFile :: [String] -> IO String
447         readTheFile [] = error $ "could not find " ++ show filename 
448                                  ++ " in path " ++ show path0
449         readTheFile (dir:dirs) = 
450                 catch (do str <- readFile (dir ++ "/" ++ filename) 
451                           return str) 
452                       (\ _ -> readTheFile dirs)