{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE TupleSections #-}
module Main where
import Control.Monad (forM_, when)
import Data.List (intercalate)
import Data.Version (showVersion)
import Diagrams.Haddock
import System.Console.CmdArgs
import System.Directory
import System.Environment (getEnvironment)
import System.FilePath ((<.>), (</>))
import Distribution.ModuleName (toFilePath)
import qualified Distribution.PackageDescription as P
import Distribution.Simple.Configure (maybeGetPersistBuildConfig)
import Distribution.Simple.LocalBuildInfo (localPkgDescr)
import Language.Preprocessor.Cpphs
import Paths_diagrams_haddock (version)
data DiagramsHaddock
= DiagramsHaddock
{ quiet :: Bool
, dataURIs :: Bool
, cacheDir :: FilePath
, outputDir :: FilePath
, distDir :: Maybe FilePath
, includeDirs :: [FilePath]
, cppDefines :: [String]
, targets :: [FilePath]
}
deriving (Show, Typeable, Data)
diagramsHaddockOpts :: DiagramsHaddock
diagramsHaddockOpts
= DiagramsHaddock
{ quiet = False
&= help "Suppress normal logging output"
, dataURIs = False
&= help "Generate embedded data URIs instead of external SVGs"
&= name "dataURIs"
, cacheDir
= ".diagrams-cache"
&= typDir
&= help "Directory for storing cached diagrams (default: .diagrams-cache)"
&= name "c"
, outputDir
= "diagrams"
&= typDir
&= help "Directory to output compiled diagrams (default: diagrams)"
, distDir
= def
&= typDir
&= help "Directory in which to look for setup-config (default: dist)"
&= name "d"
, includeDirs
= []
&= typDir
&= help "Include directory for CPP includes"
, cppDefines
= []
&= typ "NAME"
&= help "Preprocessor defines for CPP pass"
, targets
= def &= args &= typFile
}
&= program "diagrams-haddock"
&= summary (unlines
[ "diagrams-haddock v" ++ showVersion version ++ ", (c) 2013 diagrams-haddock team (see LICENSE)"
, ""
, "Compile inline diagrams code in Haddock documentation."
, ""
, "Pass diagrams-haddock the names of files to be processed, and/or"
, "the names of directories containing Cabal packages. If no arguments"
, "are given it assumes the current directory is a Cabal package."
, ""
, "For more detailed help, including details of how to create source files"
, "for diagrams-haddock to process, consult the README at"
, ""
, " http://github.com/diagrams/diagrams-haddock/"
])
main :: IO ()
main = do
opts <- cmdArgs diagramsHaddockOpts
forM_ (targets opts) $ \targ -> do
d <- doesDirectoryExist targ
f <- doesFileExist targ
case (d,f) of
(True,_) -> processCabalPackage opts targ
(_,True) -> processFile opts [] targ
_ -> targetError targ
when (null (targets opts)) $ processCabalPackage opts "."
targetError :: FilePath -> IO ()
targetError targ = putStrLn $ "Warning: target " ++ targ ++ " does not exist, ignoring."
-- | Process all the files in a Cabal package. At the moment, only
-- looks at files corresponding to exported modules from a library.
-- In other words, it does not consider the source for executables
-- or any unexported modules.
processCabalPackage :: DiagramsHaddock -> FilePath -> IO ()
processCabalPackage opts dir = do
mhsenv <- getHsenv
let fullDistDir = dir </> case (distDir opts, mhsenv) of
-- command-line arg trumps all
(Just d, _) -> d
-- next, look for active hsenv
(Nothing, Just hsenv) -> "dist_" ++ hsenv
-- otherwise, default to "dist"
_ -> "dist"
mlbi <- maybeGetPersistBuildConfig fullDistDir
case mlbi of
Nothing -> putStrLn $
"No setup-config found in " ++ fullDistDir ++ ", please run 'cabal configure' first."
Just lbi -> do
let mlib = P.library . localPkgDescr $ lbi
case mlib of
Nothing -> return ()
Just lib -> do
let buildInfo = P.libBuildInfo lib
srcDirs = P.hsSourceDirs buildInfo
includes = P.includeDirs buildInfo
defines = P.cppOptions buildInfo
opts' = opts
{ includeDirs = includeDirs opts ++ includes
}
cabalDefines = parseCabalDefines defines
mapM_ (tryProcessFile opts' cabalDefines dir srcDirs) . map toFilePath . P.exposedModules $ lib
getHsenv :: IO (Maybe String)
getHsenv = do
env <- getEnvironment
return $ lookup "HSENV_NAME" env
-- | Use @cpphs@'s options parser to handle the options from cabal.
parseCabalDefines :: [String] -> [(String,String)]
parseCabalDefines = either (const []) defines . parseOptions
-- | Try to find and process a file corresponding to a module exported
-- from a library.
tryProcessFile
:: DiagramsHaddock -- ^ options
-> [(String,String)] -- ^ additional defines
-> FilePath -- ^ base directory
-> [FilePath] -- ^ haskell-src-dirs
-> FilePath -- ^ name of the module to look for, in \"A/B/C\" form
-> IO ()
tryProcessFile opts defines dir srcDirs fileBase = do
let files = [ dir </> srcDir </> fileBase <.> ext
| srcDir <- srcDirs
, ext <- ["hs", "lhs"]
]
forM_ files $ \f -> do
e <- doesFileExist f
when e $ processFile opts defines f
-- | Process a single file with diagrams-haddock.
processFile :: DiagramsHaddock -> [(String,String)] -> FilePath -> IO ()
processFile opts defines file = do
errs <- processHaddockDiagrams' cpphsOpts (quiet opts) (dataURIs opts) (cacheDir opts) (outputDir opts) file
case errs of
[] -> return ()
_ -> putStrLn $ intercalate "\n" errs
where
cpphsOpts = defaultCpphsOptions
{ includes = includeDirs opts
, defines = map (,"") (cppDefines opts) ++ defines
, boolopts = defaultBoolOptions { hashline = False }
}