packages feed

difftodo-0.2.0: cmd/git-todo/Main.hs

-- | Get todos from git
--
-- Use git-todo to find all of the TODOs in a git repository.
--
-- e.g.
--
-- To see all the todos added between your working tree and HEAD:
--     $ git todo
--
-- To see all the todos in your branch:
--     $ git todo origin/master
--
-- To see all the todos in your code base:
--     $ git todo --files
--
-- git-todo arguments behave like git-diff arguments, unless --files is
-- specified, in which case they act like git-ls-files arguments.

-- TODO: `git todo --help` doesn't work because it needs a manpage. Try to
-- make it work somehow.

module Main (main) where

import Protolude

import qualified Data.Text as Text
import Data.Text.IO (hPutStrLn)
import GHC.IO (FilePath)
import Options.Applicative
  ( ParserInfo
  , argument
  , execParser
  , flag
  , fullDesc
  , header
  , help
  , helper
  , info
  , long
  , metavar
  , progDesc
  , short
  , switch
  , str
  )
import System.IO (stderr)
import System.Process (readProcess)

import qualified Fixme
import Fixme.Comment (Comment)


data Config = Diff Bool [FilePath] | Files [FilePath] deriving (Eq, Show)

options :: ParserInfo Config
options =
  info (helper <*> parser) description
  where
    parser = modeFlag <*> cachedFlag <*> many (argument str (metavar "FILES..."))

    modeFlag = flag Diff (const Files) (mconcat [ long "files"
                                                , short 'f'
                                                , help "Show all todos in source files instead of examining diffs"
                                                ])
    cachedFlag = switch (mconcat [ long "cached"
                                 , help "Get todos from changes staged for next commit, optionally relative to another commit. Ignored if --files is set."
                                 ])
    description = mconcat
      [ fullDesc
      , progDesc "Get todos from source code in git"
      , header "git-todo - Get todos from source code in git"
      ]

commentsFromDiff :: [FilePath] -> IO [Comment]
commentsFromDiff args =
  either abort pure . Fixme.newCommentsFromDiff =<< gitDiff args
  where
    abort e = do
      hPutStrLn stderr $ "ERROR: " <> e
      exitWith (ExitFailure 1)

commentsFromFiles :: [FilePath] -> IO [Comment]
commentsFromFiles paths =
  concatMapM commentsFrom =<< gitListFiles paths
  where
    -- If we can't figure out the language, then just assume it has no
    -- comments of interest.
    commentsFrom path = fromMaybe (pure []) (Fixme.readComments path)

-- TODO: Use gitlib

-- | Run `git diff <diffspec>` in the current working directory.
gitDiff :: [FilePath] -> IO ByteString
gitDiff args = toS <$> readProcess "git" ("diff":args) ""

-- TODO: Factor out todo reporting

-- | Run `git ls-files` with the given files in the current working directory.
gitListFiles :: [FilePath] -> IO [FilePath]
gitListFiles files =
  -- TODO: Don't assume git repo location is CWD
  toLines <$> readProcess "git" ("ls-files":files) ""
  where
    toLines = map toS . Text.lines . toS


main :: IO ()
main = do
  config <- execParser options
  comments <- case config of
                Diff False args -> commentsFromDiff args
                Diff True args  -> commentsFromDiff ("--cached":args)
                Files args      -> commentsFromFiles args
  mapM_ (putStrLn . Fixme.formatTodo) (concatMap Fixme.getTodos comments)