PageRenderTime 178ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Main.hs

http://github.com/ierton/haskdogs
Haskell | 229 lines | 161 code | 39 blank | 29 comment | 10 complexity | 224cf63294952263c9446ca6d25ce759 MD5 | raw file
Possible License(s): BSD-3-Clause
  1. {-# LANGUAGE RecordWildCards, ScopedTypeVariables, ViewPatterns #-}
  2. module Main (main) where
  3. import Control.Monad
  4. import Control.Applicative
  5. import Control.Exception
  6. import Data.Maybe
  7. import Data.List
  8. import System.IO
  9. import System.Directory
  10. import System.Process (readProcess)
  11. import System.FilePath
  12. import Options.Applicative
  13. import qualified Options.Applicative as O
  14. import Data.Version (showVersion)
  15. import qualified Paths_haskdogs as Paths
  16. {-
  17. ___ _ _
  18. / _ \ _ __ | |_(_) ___ _ __ ___
  19. | | | | '_ \| __| |/ _ \| '_ \/ __|
  20. | |_| | |_) | |_| | (_) | | | \__ \
  21. \___/| .__/ \__|_|\___/|_| |_|___/
  22. |_|
  23. -}
  24. data Opts = Opts {
  25. cli_dirlist_file :: FilePath
  26. , cli_filelist_file :: FilePath
  27. , cli_hasktags_args1 :: String
  28. , cli_ghc_pkgs_args :: String
  29. , cli_use_stack :: Tristate
  30. -- , cli_use_sandbox :: Tristate
  31. , cli_hasktags_args2 :: [String]
  32. } deriving(Show)
  33. data Tristate = ON | OFF | AUTO
  34. deriving(Eq, Ord, Show, Read)
  35. def_hasktags_args = words "-c -x"
  36. optsParser :: Parser Opts
  37. optsParser = Opts
  38. <$> strOption (
  39. long "dir-list" <>
  40. short 'd' <>
  41. metavar "FILE" <>
  42. value "" <>
  43. help "File containing directory list to process" )
  44. <*> strOption (
  45. long "file-list" <>
  46. short 'f' <>
  47. metavar "FILE" <>
  48. value "" <>
  49. help "File containing Haskell sources to process" )
  50. <*> strOption (
  51. long "hasktags-args" <>
  52. metavar "OPTS" <>
  53. value "" <>
  54. help ("Arguments to pass to hasktags. " ++ unwords def_hasktags_args ++ " is the default"))
  55. <*> strOption (
  56. long "ghc-pkg-args" <>
  57. metavar "OPTS" <>
  58. value "" <>
  59. help "Arguments to pass to ghc-pkgs")
  60. <*> option auto (
  61. long "use-stack" <>
  62. value AUTO <>
  63. help "Execute ghc-pkg via stack, arg is ON, OFF or AUTO (the default)")
  64. -- <*> option auto (
  65. -- long "include-sandbox" <>
  66. -- value AUTO <>
  67. -- help "(!UNIMPLEMENTED!) Include .cabal-sandbox package databases")
  68. <*> many (argument str (metavar "OPTS" <> help "More hasktags options, use `--' to pass flags starting with `-'"))
  69. exename :: String
  70. exename = "haskdogs"
  71. versionParser :: Parser (a -> a)
  72. versionParser = infoOption (exename ++ " version " ++ (showVersion Paths.version))
  73. (long "version" <> help "Show version number")
  74. opts = info (helper <*> versionParser <*> optsParser)
  75. ( fullDesc <> header (exename ++ " - Recursive hasktags-based TAGS generator for a Haskell project" ))
  76. {-
  77. __ __ _
  78. | \/ | __ _(_)_ __
  79. | |\/| |/ _` | | '_ \
  80. | | | | (_| | | | | |
  81. |_| |_|\__,_|_|_| |_|
  82. -}
  83. main :: IO()
  84. main = do
  85. Opts {..} <- execParser opts
  86. let
  87. -- Directory to unpack sources into
  88. getDataDir :: IO FilePath
  89. getDataDir = do
  90. x <- (</> ".haskdogs") <$> getHomeDirectory
  91. createDirectoryIfMissing False x
  92. return x
  93. cli_verbose = True
  94. vprint a
  95. | cli_verbose = eprint a
  96. | otherwise = return ()
  97. eprint = hPutStrLn stderr
  98. runp nm args inp = do
  99. vprint $ nm ++ " " ++ unwords args
  100. readProcess nm args inp
  101. -- Run GNU which tool
  102. checkapp :: String -> IO ()
  103. checkapp appname = do
  104. (runp "which" [appname] [] >> return ()) `onException`
  105. (eprint ("Please Install \"" ++ appname ++ "\" application"))
  106. hasapp :: String -> IO Bool
  107. hasapp appname = do
  108. vprint $ "Cheking for " ++ appname ++ " with GNU which"
  109. (runp "which" [appname] [] >> return True) `catch`
  110. (\(e::SomeException) -> vprint ("GNU which falied to find " ++ appname) >> return False)
  111. cwd <- getCurrentDirectory
  112. datadir <- getDataDir
  113. has_stack <- hasapp "stack"
  114. let
  115. readLinedFile f =
  116. lines <$> (hGetContents =<< (
  117. if (f=="-")
  118. then return stdin
  119. else openFile f ReadMode))
  120. readDirFile :: IO [FilePath]
  121. readDirFile
  122. | null cli_dirlist_file && null cli_filelist_file = return ["."]
  123. | null cli_dirlist_file = return []
  124. | otherwise = readLinedFile cli_dirlist_file
  125. readSourceFile :: IO [FilePath]
  126. readSourceFile
  127. | null cli_filelist_file = return []
  128. | otherwise = readLinedFile cli_filelist_file
  129. cli_hasktags_args = (words cli_hasktags_args1) ++ cli_hasktags_args2
  130. runp_ghc_pkgs args = go cli_use_stack where
  131. go ON = runp "stack" (["exec", "ghc-pkg", "--"] ++ args) []
  132. go OFF = runp "ghc-pkg" args []
  133. go AUTO = if has_stack then go ON else go OFF
  134. cabal_or_stack = go cli_use_stack where
  135. go ON = "stack"
  136. go OFF = "cabal"
  137. go AUTO = if has_stack then go ON else go OFF
  138. -- Finds *hs in dirs, but filter-out Setup.hs
  139. findSources :: [FilePath] -> IO [FilePath]
  140. findSources dirs =
  141. filter (not . isSuffixOf "Setup.hs") . lines <$>
  142. runp "find" (dirs ++ words "-type f -and ( -name *\\.hs -or -name *\\.lhs -or -name *\\.hsc )") []
  143. grepImports :: String -> Maybe String
  144. grepImports line = case words line of
  145. ("import":"qualified":x:_) -> Just (filter (/=';') x)
  146. ("import":x:_) -> Just (filter (/=';') x)
  147. _ -> Nothing
  148. -- Produces list of imported modules for file.hs given
  149. findModules :: [FilePath] -> IO [String]
  150. findModules files = (catMaybes . map grepImports . lines) <$> runp "cat" files []
  151. -- Maps import name to haskell package name
  152. iname2module :: String -> IO (Maybe String)
  153. iname2module iname = do
  154. mod <- (listToMaybe . words) <$> (runp_ghc_pkgs ["--simple-output", "find-module", iname])
  155. vprint $ "Import " ++ iname ++ " resolved to " ++ (fromMaybe "NULL" mod)
  156. return mod
  157. inames2modules :: [String] -> IO [FilePath]
  158. inames2modules is = nub . sort . catMaybes <$> forM (nub is) iname2module
  159. -- Unapcks haskel package to the sourcedir
  160. unpackModule :: FilePath -> IO (Maybe FilePath)
  161. unpackModule ((datadir</>) -> p) = do
  162. exists <- doesDirectoryExist (datadir</>p)
  163. case exists of
  164. True -> do
  165. vprint $ "Already unpacked " ++ p
  166. return (Just p)
  167. False -> do
  168. bracket_ (setCurrentDirectory datadir) (setCurrentDirectory cwd) $
  169. ( runp cabal_or_stack ["unpack", p] [] >> return (Just p)
  170. ) `catch`
  171. (\(_ :: SomeException) ->
  172. eprint ("Can't unpack " ++ p) >> return Nothing
  173. )
  174. unpackModules :: [FilePath] -> IO [FilePath]
  175. unpackModules ms = catMaybes <$> mapM unpackModule ms
  176. gentags :: IO ()
  177. gentags = do
  178. checkapp "hasktags"
  179. files <- do
  180. dirs <- readDirFile
  181. ss_local <- (++) <$> readSourceFile <*> findSources dirs
  182. when (null ss_local) $ do
  183. fail $ "Haskdogs were not able to find any sources in " <> (intercalate ", " dirs)
  184. ss_l1deps <- findModules ss_local >>= inames2modules >>= unpackModules >>= findSources
  185. return $ ss_local ++ ss_l1deps
  186. runp "hasktags" ((if null cli_hasktags_args then def_hasktags_args else cli_hasktags_args) ++ files) []
  187. putStrLn "\nSuccess"
  188. {- _real_main_ -}
  189. gentags