packages feed

sensei-0.8.0: src/Util.hs

{-# LANGUAGE CPP #-}
module Util (
  Color(..)
, withColor
, withInfoColor
, encodeUtf8
, isBoring
, filterGitIgnoredFiles
, normalizeTypeSignatures
, isWritableByOthers

#ifdef TEST
, filterGitIgnoredFiles_
, gitCheckIgnoreFeedback
, writableByOthers
#endif
) where

import           Imports

import           System.Console.ANSI
import           System.Process
import           System.Posix.Files
import           System.Posix.Types
import qualified Data.Text as T
import qualified Data.Text.Encoding as T

withInfoColor :: String -> String
withInfoColor = withColor Magenta

withColor :: Color -> String -> String
withColor c string =  set <> string <> reset
  where
    set = setSGRCode [SetColor Foreground Dull c]
    reset = setSGRCode []

encodeUtf8 :: String -> ByteString
encodeUtf8 = T.encodeUtf8 . T.pack

isBoring :: FilePath -> Bool
isBoring p = ".git" `elem` dirs || "dist" `elem` dirs || isEmacsAutoSave p
  where
    dirs = splitDirectories p
    isEmacsAutoSave = isPrefixOf ".#" . takeFileName

filterGitIgnoredFiles :: (String -> IO ()) -> FilePath -> [FilePath] -> IO [FilePath]
filterGitIgnoredFiles echo dir files = do
  (feedback, ignoredFiles) <- filterGitIgnoredFiles_ dir files
  printFeedback feedback
  return ignoredFiles
  where
    printFeedback :: Feedback -> IO ()
    printFeedback = mapM_ $ \ (color, err) -> echo ('\n' : withColor color err <> "\n")

type Feedback = Maybe (Color, String)

filterGitIgnoredFiles_ :: FilePath -> [FilePath] -> IO (Feedback, [FilePath])
filterGitIgnoredFiles_ dir files = fmap (files \\) <$> gitCheckIgnore dir files

gitCheckIgnore :: FilePath -> [FilePath] -> IO (Feedback, [FilePath])
gitCheckIgnore dir files = do
  (_, ignoredFiles, err) <- readProcessWithExitCode "git" ["-C", dir, "check-ignore", "--stdin", "-z"] $ join_ files
  return (gitCheckIgnoreFeedback err, split ignoredFiles)
  where
    join_ = intercalate "\0"
    split = map T.unpack . T.split (== '\0') . T.pack

gitCheckIgnoreFeedback :: String -> Feedback
gitCheckIgnoreFeedback err
  | "fatal: not a git repository (or any " `isPrefixOf` err = notGitWarning
  | err == "" = Nothing
  | otherwise = Just (Red, err)
  where
    notGitWarning = Just (Cyan, "warning: not a git repository - .gitignore support not available\n")

normalizeTypeSignatures :: String -> String
normalizeTypeSignatures = normalize . concatMap replace
  where
    normalize = \case
      '\n' : ' ' : ' ' : xs -> normalizeTypeSignatures (' ' : dropWhile (== ' ') xs)
      x : xs -> x : normalizeTypeSignatures xs
      [] -> []

    replace c = case c of
      '\8759' -> "::"
      '\8594' -> "->"
      _ -> [c]

isWritableByOthers :: FilePath -> IO Bool
isWritableByOthers name = do
  exists <- fileExist name
  if exists then do
    mode <- fileMode <$> getFileStatus name
    dirMode <- fileMode <$> getFileStatus (takeDirectory name)
    return (writableByOthers mode || writableByOthers dirMode)
  else
    return False

writableByOthers :: FileMode -> Bool
writableByOthers mode = m /= nullFileMode
  where
    m = intersectFileModes (unionFileModes otherWriteMode groupWriteMode) mode