packages feed

PandocAgda-2.3.3: src/full/Agda/Interaction/Highlighting/PandocHTML.hs

{-# LANGUAGE CPP #-}

-- | Function for generating highlighted, hyperlinked HTML from Agda
-- sources.

module Agda.Interaction.Highlighting.PandocHTML
  ( generateHTML
  ) where

import Control.Applicative
import Control.Monad
import Control.Monad.Trans
import Control.Monad.State.Class
import Control.Arrow ((***))
import System.FilePath
import System.Directory
import Text.XHtml.Strict
import Data.Function
import Data.Monoid
import Data.Maybe
import qualified Data.Map  as Map
import qualified Data.List as List
import qualified Data.Char as Char

import Text.Pandoc.Definition
import qualified Text.Pandoc.Definition as Pandoc
import Text.Pandoc.Options
import Text.Pandoc.Readers.Markdown
import Text.Pandoc.Writers.HTML

import Paths_PandocAgda

import Agda.Interaction.FindFile
import Agda.Interaction.Highlighting.Generate
import Agda.Interaction.Highlighting.Precise
import Agda.Interaction.Highlighting.Range
import Agda.TypeChecking.Monad (TCM)
import qualified Agda.TypeChecking.Monad as TCM
import qualified Agda.Syntax.Abstract as A
import qualified Agda.Syntax.Concrete as C
import Agda.Syntax.Common
import qualified Agda.Syntax.Scope.Monad as Scope
import Agda.Syntax.Translation.ConcreteToAbstract
import Agda.Interaction.Options
import qualified Agda.Interaction.PandocOptions as PO
import Agda.Utils.FileName (filePath)
import qualified Agda.Utils.IO.UTF8 as UTF8
import Agda.Utils.Pretty

import Agda.Utils.Impossible
#include "../../undefined.h"

-- | The name of the default CSS file.

defaultCSSFile :: FilePath
defaultCSSFile = "Agda.css"

-- | The name of the default template file for pandoc.

defaultTemplateFile :: FilePath
defaultTemplateFile = "Agda.template"

-- | Generates HTML files from all the sources which the given module
-- depends on (including the module itself).
--
-- This function should only be called after type checking has
-- completed successfully.

generateHTML :: PO.CommandLineOptions -> A.ModuleName -> TCM ()
generateHTML fullopts mod = do
      options <- TCM.commandLineOptions

      -- There is a default directory given by 'defaultHTMLDir'
      let dir = optHTMLDir options
      liftIO $ createDirectoryIfMissing True dir

      -- If the default CSS file should be used, then it is copied to
      -- the output directory.
      liftIO $ when (isNothing $ optCSSFile options) $ do
        cssFile <- getDataFileName defaultCSSFile
        copyFile cssFile (dir </> defaultCSSFile)

      TCM.reportSLn "html" 1 $ unlines
        [ ""
        , "Warning: HTML is currently generated for ALL files which can be"
        , "reached from the given module, including library files."
        ]

      -- Pull highlighting info from the state and generate all the
      -- web pages.
      mapM_ (\(m, h) -> generatePage fullopts dir m h) =<<
        map (id *** TCM.iHighlighting . TCM.miInterface) .
          Map.toList <$> TCM.getVisitedModules

-- | Converts module names to the corresponding HTML file names.

modToFile :: C.TopLevelModuleName -> FilePath
modToFile m = render (pretty m) <.> "html"

-- | Generates an HTML file with a highlighted, hyperlinked version of
-- the given module.

generatePage
  :: PO.CommandLineOptions
  -> FilePath              -- ^ Directory in which to create files.
  -> C.TopLevelModuleName  -- ^ Module to be highlighted.
  -> HighlightingInfo      -- ^ Syntax highlighting info for the module.
  -> TCM ()
generatePage fullopts dir mod highlighting = do
  mf <- Map.lookup mod . TCM.stModuleToSource <$> get
  case mf of
    Nothing -> __IMPOSSIBLE__
    Just f  -> do
      contents <- liftIO $ UTF8.readTextFile $ filePath f
      css      <- maybe defaultCSSFile id . optCSSFile <$>
                    TCM.commandLineOptions
      let mSlideVariant = fmap read $ PO.optGenerateHTML fullopts
      html <- case mSlideVariant of
          Just slideVariant | isLiterate (filePath f) -> do
              templateFile <- maybe (liftIO $ getDataFileName defaultTemplateFile) return
                            (PO.optTemplateFile fullopts)
              template <- liftIO $ UTF8.readTextFile templateFile
              return $ pandocPage template css slideVariant mod contents highlighting
          _ -> do
              return $ renderHtml $ page css mod contents highlighting
      TCM.reportSLn "html" 1 $ "Generating HTML for " ++
                               render (pretty mod) ++
                               " (" ++ target ++ ")."
      liftIO $ UTF8.writeFile target html
  where target = dir </> modToFile mod


-- | Constructs the web page, including headers.

page :: FilePath              -- ^ URL to the CSS file.
     -> C.TopLevelModuleName  -- ^ Module to be highlighted.
     -> String                -- ^ The contents of the module.
     -> CompressedFile        -- ^ Highlighting information.
     -> Html
page css modName contents info =
  header (thetitle << render (pretty modName)
            +++
          meta ! [ httpequiv "Content-Type"
                 , content "text/html; charset=UTF-8"
                 ]
            +++
          meta ! [ httpequiv "Content-Style-Type"
                 , content "text/css"
                 ]
            +++
          thelink noHtml ! [ href css
                           , rel "stylesheet"
                           , thetype "text/css"
                           ])
  +++
  body << pre << code contents info

--- | Constructs the HTML displaying the code.

code :: String         -- ^ The contents of the module.
     -> CompressedFile -- ^ Highlighting information.
     -> Html
code contents info =
  mconcat $
  map (\(pos, s, mi) -> annotate pos mi (stringToHtml s)) $
  tokenStream contents info

-- | Constructs the web page, including headers, with pandoc.

pandocPage
     :: String                -- ^ Template
     -> FilePath              -- ^ URL to the CSS file.
     -> HTMLSlideVariant      -- ^ Slide variant
     -> C.TopLevelModuleName  -- ^ Module to be highlighted.
     -> String                -- ^ The contents of the module.
     -> CompressedFile        -- ^ Highlighting information.
     -> String
pandocPage template css slideVariant modName contents info =
  writeHtmlString def  { writerStandalone = True
                       , writerTemplate = template
                       , writerTableOfContents = True
                       , writerSlideVariant = slideVariant
                       , writerVariables = [("css",css)]
                       }
    $ Pandoc meta $ concat $ zipWith ($) (removeCodeBlocks md []) cf
 where
  cf = (++ [[]])
     . map conv
     . getCode
     . splitComment "\n\\begin{code}"
     . splitComment "\n\\end{code}"
     $ tokenStream contents info

  (Pandoc meta md) = readMarkdown def contents 

  conv x =  [Para [ RawInline "html"
                  . renderHtmlFragment
                  . (pre <<) 
                  . mconcat
                  . map (\(pos, s, mi) -> annotate pos mi (stringToHtml s))
                  $ x
                  ]]

  getCode ((_,s,_):xs)
    | s == "\n\\begin{code}" || List.isPrefixOf "\\begin{code}" s
    = getCode' xs []
  getCode (_:xs) = getCode xs
  getCode [] = []

  getCode' ((_,s,_):xs)
    | s == "\n\\end{code}" || List.isPrefixOf "\\end{code}" s
    = (: getCode xs) . reverse
  getCode' (x:xs) = getCode' xs . (x:)
  getCode' [] = error "__IMPOSSIBLE__" -- ???

  splitComment w = concatMap f
    where
      f (pos, s, mi) = [(pos, t, mi) | t <- filter (not . null) $ g s []]

      g s | List.isPrefixOf w s = (: (w : g (drop (length w) s) [])) . reverse
      g [] = (:[]) . reverse
      g (c:cs) = g cs . (c:)

  removeCodeBlocks (RawBlock "latex" s: xs) | List.isPrefixOf "\\begin{code}" s
        = removeCodeBlocks' (++) xs
  removeCodeBlocks (RawBlock "html" s: xs) | List.isPrefixOf "<!--\n\\begin{code}" s
        = removeCodeBlocks' const xs
  removeCodeBlocks xs@(Para (Pandoc.Str "\\begin{code}":_):_)
        = removeCodeBlocks' (++) (drop 1 $ dropWhile (not . end) xs)
       where
        end (Para xs@(_:_)) = last xs == Pandoc.Str "\\end{code}"
        end _ = False
  removeCodeBlocks (x:xs) = removeCodeBlocks xs . (x:)
  removeCodeBlocks [] = removeCodeBlocks' (++) []

  removeCodeBlocks' f xs acc = f (reverse acc): case xs of
    []  -> []
    xs  -> removeCodeBlocks xs []

-- | Constructs token stream ready to print.

tokenStream
     :: String         -- ^ The contents of the module.
     -> CompressedFile -- ^ Highlighting information.
     -> [(Integer, String, MetaInfo)]  -- ^ (position, contents, info)
tokenStream contents info =
  map (\cs -> case cs of
          (mi, (pos, _)) : _ ->
            (pos, map (snd . snd) cs, maybe mempty id mi)
          [] -> __IMPOSSIBLE__) $
  List.groupBy ((==) `on` fst) $
  map (\(pos, c) -> (Map.lookup pos infoMap, (pos, c))) $
  zip [1..] contents
  where
  infoMap = toMap (decompress info)

annotate :: Integer -> MetaInfo -> Html -> Html
annotate pos mi = anchor ! attributes
  where
    attributes =
      [name (show pos)] ++
      maybe [] link (definitionSite mi) ++
      (case classes of
        [] -> []
        cs -> [theclass $ unwords cs])

    classes =
      maybe [] noteClasses (note mi)
      ++ otherAspectClasses (otherAspects mi)
      ++ maybe [] aspectClasses (aspect mi)

    aspectClasses (Name mKind op) = kindClass ++ opClass
      where
      kindClass = maybe [] ((: []) . showKind) mKind

      showKind (Constructor Inductive)   = "InductiveConstructor"
      showKind (Constructor CoInductive) = "CoinductiveConstructor"
      showKind k                         = show k

      opClass = if op then ["Operator"] else []
    aspectClasses a = [show a]

    otherAspectClasses = map show

    -- Notes are not included.
    noteClasses s = []

    link (m, pos) = [href $ modToFile m ++ "#" ++ show pos]