haskdogs 0.3.2 → 0.4.0
raw patch · 4 files changed
+269/−159 lines, 4 filesdep +bytestringdep +directorydep +optparse-applicativedep −Cabaldep −HSHdep ~basedep ~filepath
Dependencies added: bytestring, directory, optparse-applicative, process, text
Dependencies removed: Cabal, HSH
Dependency ranges changed: base, filepath
Files
- README.md +14/−12
- haskdogs.cabal +28/−22
- src/Main.hs +227/−0
- src/haskdogs.hs +0/−125
README.md view
@@ -11,21 +11,21 @@ he/she uses. Note, that haskdogs relies on some GNU programs as well as on Unix shell-commands such as 'cd', 'mkdir' and so on. Also it would run 'cabal' and ghc-pkg'+commands such as 'cd', 'mkdir' and so on. Also it would run 'stack' and ghc-pkg' in order to obtain package information. INSTALL ------- -Check the dependencies. Currently they are: cabal, ghc, hasktags, GNU find,+Check the dependencies. Currently they are: stack, hasktags, GNU find, which and shell. -0. cabal install hasktags haskdogs-1. git clone https://github.com/ierton/haskdogs-2. cd haskdogs-3. cabal configure && cabal install-4. export PATH="$HOME/.cabal/bin:$PATH"+Please follow stack's documentation(https://github.com/commercialhaskell/stack) to install stack. + $ stack install hasktags haskdogs++Make sure that PATH contains path to your stack binaries directory ($HOME/.local/bin by default).+ RUNNING ------- @@ -33,12 +33,14 @@ 2. cd to your Haskell project dir - $ cd $HOME/my-haskell-project+ $ cd $HOME/my-haskell-project -3. run haskdogs (cmdline args will be passed to hasktags followed by a filelist generated)+3. Run haskdogs without arguments to generate tags file in Vim-compatible format - $ haskdogs+ $ haskdogs +Emacs users would probably want to add -e option to build Emacs-compatible TAGS.+ VIM HINT -------- @@ -78,7 +80,7 @@ Just copy the code above to your ~/.vimrc and reload the vim. ---Sergey -<ierton@gmail.com>+Sergey+<grrwlf@gmail.com>
haskdogs.cabal view
@@ -1,41 +1,47 @@--- haskdogs.cabal auto-generated by cabal init. For additional--- options, see--- http://www.haskell.org/cabal/release/cabal-latest/doc/users-guide/authors.html#pkg-descr.--- The name of the package. Name: haskdogs-Version: 0.3.2-Synopsis: Generate ctags file for haskell project directory and it's deps-Homepage: http://github.com/ierton/haskdogs+Version: 0.4.0+Synopsis: Generate tags file for Haskell project and its nearest deps+Homepage: http://github.com/grwlf/haskdogs License: BSD3 License-file: LICENSE Author: Sergey Mironov-Maintainer: ierton@gmail.com+Maintainer: grrwlf@gmail.com Category: Development Build-type: Simple-Cabal-version: >=1.6+Cabal-version: >=1.22 extra-source-files: README.md Description:- haskdogs is a small shellscript-like tool which creates tag file for entire- haskell project directory. It takes into account first-level dependencies by- recursively scanning imports and adding matching packages to the final- tag list.+ Haskdogs is a 300-lines tool which creates tag file for entire Haskell+ project directory. It takes into account first-level dependencies by+ recursively scanning imports and adding matching packages to the final tag+ list. - As a result, programmer can use his/her text editor supporting tags (vim, for- example) to jump directly to definition of any standard or foreign function- he/she uses.+ As a result, programmer can use his/her text editor supporting tags (vim,+ for example) to jump directly to definition of any standard or foreign+ function he/she uses. - Note, that haskdogs calls some Unix shell commands like 'test' or 'mkdir'- so this tool will likely fail to work on pure Windows platforms.+ Note, that haskdogs calls some Unix shell commands like 'grep' so this tool+ will likely fail to work on pure Windows platforms. - Starting from 0.3, cmdline args will be passed to hasktags followed by a + Starting from 0.3, cmdline args will be passed to hasktags followed by a filelist generated. + Starting from 0.4, haskdogs will use stack toolchain.+ Executable haskdogs+ Default-language: Haskell2010 Hs-source-dirs: src- Main-is: haskdogs.hs- Build-depends: base >= 3 && < 5, Cabal >= 1.6, HSH >= 2.0.3, filepath >= 1.1.0.3+ Main-is: Main.hs+ Build-depends: base >= 4.8 && < 5+ , filepath+ , bytestring+ , text+ , directory+ , optparse-applicative+ , process+ Ghc-options: -fwarn-tabs- + Source-repository head Type: git Location: http://github.com/ierton/haskdogs
+ src/Main.hs view
@@ -0,0 +1,227 @@+{-# LANGUAGE RecordWildCards, ScopedTypeVariables, ViewPatterns #-}+module Main (main) where++import Control.Monad+import Control.Applicative+import Control.Exception+import Data.Maybe+import Data.List+import System.IO+import System.Directory+import System.Process (readProcess)+import System.FilePath++import Options.Applicative+import qualified Options.Applicative as O+import Data.Version++{-+ ___ _ _+ / _ \ _ __ | |_(_) ___ _ __ ___+| | | | '_ \| __| |/ _ \| '_ \/ __|+| |_| | |_) | |_| | (_) | | | \__ \+ \___/| .__/ \__|_|\___/|_| |_|___/+ |_|+-}++data Opts = Opts {+ cli_dirlist_file :: FilePath+ , cli_filelist_file :: FilePath+ , cli_hasktags_args1 :: String+ , cli_ghc_pkgs_args :: String+ , cli_use_stack :: Tristate+ , cli_use_sandbox :: Tristate+ , cli_hasktags_args2 :: [String]+ } deriving(Show)++data Tristate = ON | OFF | AUTO+ deriving(Eq, Ord, Show, Read)++optsParser :: Parser Opts+optsParser = Opts+ <$> strOption (+ long "dir-list" <>+ short 'd' <>+ metavar "FILE" <>+ value "" <>+ help "File containing directory list to process" )+ <*> strOption (+ long "file-list" <>+ short 'f' <>+ metavar "FILE" <>+ value "" <>+ help "File containing Haskell sources to process" )+ <*> strOption (+ long "hasktags-args" <>+ metavar "OPTS" <>+ value "-c -x" <>+ help "Arguments to pass to hasktags")+ <*> strOption (+ long "ghc-pkg-args" <>+ metavar "OPTS" <>+ value "" <>+ help "Arguments to pass to ghc-pkgs")+ <*> option auto (+ long "use-stack" <>+ value AUTO <>+ help "Execute ghc-pkg via stack")+ <*> option auto (+ long "include-sandbox" <>+ value AUTO <>+ help "(!UNIMPLEMENTED!) Include .cabal-sandbox package databases")+ <*> many (argument str (metavar "OPTS" <> help "More hasktags options"))++exename, versionId :: String+exename = "haskdogs"+versionId = "0.4.0"++version :: Parser (a -> a)+version = infoOption (exename ++ " version " ++ versionId)+ (long "version" <> help "Show version number")++opts = info (helper <*> version <*> optsParser)+ ( fullDesc <> header (exename ++ " - Recursive hasktags-based TAGS generator for a Haskell project" ))++{-+ __ __ _+| \/ | __ _(_)_ __+| |\/| |/ _` | | '_ \+| | | | (_| | | | | |+|_| |_|\__,_|_|_| |_|++-}++main :: IO()+main = do++ Opts {..} <- execParser opts++ let+ -- Directory to unpack sources into+ getDataDir :: IO FilePath+ getDataDir = do+ x <- (</> ".haskdogs") <$> getHomeDirectory+ createDirectoryIfMissing False x+ return x++ cli_verbose = True++ vprint a+ | cli_verbose = eprint a+ | otherwise = return ()++ eprint = hPutStrLn stderr++ runp nm args inp = do+ vprint $ nm ++ " " ++ unwords args+ readProcess nm args inp++ -- Run GNU which tool+ checkapp :: String -> IO ()+ checkapp appname = do+ (runp "which" [appname] [] >> return ()) `onException`+ (eprint ("Please Install \"" ++ appname ++ "\" application"))++ hasapp :: String -> IO Bool+ hasapp appname = do+ vprint $ "Cheking for " ++ appname ++ " with GNU which"+ (runp "which" [appname] [] >> return True) `catch`+ (\(e::SomeException) -> vprint ("GNU which falied to find " ++ appname) >> return False)++ cwd <- getCurrentDirectory+ datadir <- getDataDir+ has_stack <- hasapp "stack"++ let++ readLinedFile f =+ lines <$> (hGetContents =<< (+ if (f=="-")+ then return stdin+ else openFile f ReadMode))++ readDirFile :: IO [FilePath]+ readDirFile+ | null cli_dirlist_file && null cli_filelist_file = return ["."]+ | null cli_dirlist_file = return []+ | otherwise = readLinedFile cli_dirlist_file++ readSourceFile :: IO [FilePath]+ readSourceFile+ | null cli_filelist_file = return []+ | otherwise = readLinedFile cli_filelist_file++ cli_hasktags_args = (words cli_hasktags_args1) ++ cli_hasktags_args2++ runp_ghc_pkgs args = go cli_use_stack where+ go ON = runp "stack" (["exec", "ghc-pkg", "--"] ++ args) []+ go OFF = runp "ghc-pkg" args []+ go AUTO = if has_stack then go ON else go OFF++ cabal_or_stack = go cli_use_stack where+ go ON = "stack"+ go OFF = "cabal"+ go AUTO = if has_stack then go ON else go OFF++ -- Finds *hs in dirs, but filter-out Setup.hs+ findSources :: [FilePath] -> IO [FilePath]+ findSources dirs =+ filter (not . isSuffixOf "Setup.hs") . lines <$>+ runp "find" (dirs ++ words "-type f -and ( -name *\\.hs -or -name *\\.lhs -or -name *\\.hsc )") []++ grepImports :: String -> Maybe String+ grepImports line = case words line of+ ("import":"qualified":x:_) -> Just x+ ("import":x:_) -> Just x+ _ -> Nothing++ -- Produces list of imported modules for file.hs given+ findModules :: [FilePath] -> IO [String]+ findModules files = (catMaybes . map grepImports . lines) <$> runp "cat" files []++ -- Maps import name to haskell package name+ iname2module :: String -> IO (Maybe String)+ iname2module iname = do+ mod <- (listToMaybe . words) <$> (runp_ghc_pkgs ["--simple-output", "find-module", iname])+ vprint $ "Import " ++ iname ++ " resolved to " ++ (fromMaybe "NULL" mod)+ return mod++ inames2modules :: [String] -> IO [FilePath]+ inames2modules is = nub . sort . catMaybes <$> forM (nub is) iname2module++ -- Unapcks haskel package to the sourcedir+ unpackModule :: FilePath -> IO (Maybe FilePath)+ unpackModule ((datadir</>) -> p) = do+ exists <- doesDirectoryExist (datadir</>p)+ case exists of+ True -> do+ vprint $ "Already unpacked " ++ p+ return (Just p)+ False -> do+ bracket_ (setCurrentDirectory datadir) (setCurrentDirectory cwd) $+ ( runp cabal_or_stack ["unpack", p] [] >> return (Just p)+ ) `catch`+ (\(_ :: SomeException) ->+ eprint ("Can't unpack " ++ p) >> return Nothing+ )++ unpackModules :: [FilePath] -> IO [FilePath]+ unpackModules ms = catMaybes <$> mapM unpackModule ms++ gentags :: IO ()+ gentags = do+ checkapp "hasktags"+ files <- do+ dirs <- readDirFile+ ss_local <- (++) <$> readSourceFile <*> findSources dirs+ when (null ss_local) $ do+ fail $ "haskdogs were not able to find any sources in " <> (intercalate ", " dirs)+ ss_l1deps <- findModules ss_local >>= inames2modules >>= unpackModules >>= findSources+ return $ ss_local ++ ss_l1deps+ runp "hasktags" (cli_hasktags_args ++ files) []+ return ()++ {- _real_main_ -}+ gentags++
− src/haskdogs.hs
@@ -1,125 +0,0 @@-module Main (main) where--import HSH-import Data.List-import Control.Applicative-import Control.Monad-import System.IO-import System.Exit-import System.Environment-import System.FilePath--eprint = hPutStrLn stderr---- GNU find command line-find_in_dirs dirs p = ("find", dirs ++ (words "-type f -and -name") ++ [p])---- ghc-pkg command line-ghc_pkg_find m = ("ghc-pkg", ["find-module", m])---- cabal unpack command line-cabal_unpack p = ("cabal", ["unpack", p])---- Finds *hs in current dir, recursively-findSources :: [String] -> IO [String]-findSources [] = return []-findSources d = run $ find_in_dirs d "*\\.hs"---- Produces list of imported modules for file.hs given-findImports :: [String] -> IO [String]-findImports s = run $ catFrom s -|- extractImports---- Search for 'import' declarations-extractImports = nub . sort . filter (/=[]) . map (grepImports . words)--grepImports ("import":"qualified":x:_) = x-grepImports ("import":x:_) = x-grepImports _ = []---- Maps import name to haskell package name-iname2module :: String -> IO String-iname2module m = run $ ghc_pkg_find m -|- egrep "^ +[a-zA-Z]" -|- map (head . words) -|- highver- where highver [] = []- highver s = last (lines s)--inames2modules :: [String] -> IO [String]-inames2modules is = forM is (iname2module) >>= return . nub . sort . filter (/=[])--testdir dir fyes fno = do- ret <- run ("test",["-d", dir])- case ret of- ExitSuccess -> fyes- _ -> fno---- Unapcks haskel package to the sourcedir-unpackModule p = do- srcdir <- sourcedir- let fullpath = srcdir </> p- testdir fullpath- (do- eprint $ "Already unpacked " ++ p- return fullpath- )- (do- cd srcdir- ec <- tryEC (runIO (cabal_unpack p))- case ec of- Left _ -> return ""- Right _ -> return fullpath- )--unpackModules ms = filter (/="") <$> mapM unpackModule ms---- Run GNU which tool-which :: String -> IO (String, IO (String,ExitCode))-which n = run ("which", [n])--checkapp appname = do- (_,ec) <- which appname >>= return . snd >>= id- case ec of- ExitSuccess -> return ()- _ -> do- eprint $ "Please Install \"" ++ appname ++ "\" application"- exitWith ec---- Directory to unpack sources into-sourcedir = glob "~" >>= return . (</> ".haskdogs") . head--gentags dirs flags = do- checkapp "cabal"- checkapp "ghc-pkg"- checkapp "hasktags"- d <- sourcedir- testdir d (return ()) (run ("mkdir",["-p",d]))- files <- bracketCD "." $ do- ss_local <- findSources dirs- when (null ss_local) $ do- fail $ "haskdogs were not able to find any sources in " ++ (unwords dirs)- ss_l1deps <- findImports ss_local >>= inames2modules >>= unpackModules >>= findSources- return $ ss_local ++ ss_l1deps- runIO $ ("hasktags", flags ++ files)--help = do- eprint "haskdogs: generates tags file for haskell project directory"- eprint "Usage:"- eprint " haskdogs [-d (FILE|'-')] [FLAGS]"- eprint " FLAGS will be passed to hasktags as-is followed by"- eprint " a list of files. Defaults to -c -x."- return ()--defflags = ["-c", "-x"]--amain [] = gentags ["."] defflags-amain ("-d" : dirfile : flags) = do- file <- if (dirfile=="-") then return stdin else openFile dirfile ReadMode- dirs <- lines <$> hGetContents file- gentags dirs (if null flags then defflags else flags)-amain flags - | "-h" `elem` flags = help- | "--help" `elem` flags = help- | "-?" `elem` flags = help- | otherwise = gentags ["."] flags--main :: IO()-main = getArgs >>= amain-