dot-linker-0.1.0.0: src/DotLinker.hs
{-# LANGUAGE OverloadedStrings #-}
module DotLinker
( lineMapParser
, fileMapParser
, matchAndLink
, expandPath
, Entry
) where
import Control.Applicative
import Control.Monad (forM_, unless, when)
import Data.Attoparsec.ByteString.Char8
import qualified Data.HashMap.Strict as M
import Data.List (foldl')
import Data.Text (Text, pack, unpack)
import Data.Text.Encoding (decodeUtf8)
import Filesystem.Path.CurrentOS (FilePath, encode)
import System.Posix.Files (createSymbolicLink)
import Prelude hiding (takeWhile, FilePath)
import Turtle ((</>), (<>), MonadIO, fromText, toText, liftIO)
import qualified Turtle as T
import DotLinker.Parsers
type MappedDots = M.HashMap Text [FilePath]
-- | Given a map of dot files to their location and a dot file, make a symbolic
-- link for the given dotfile IFF a mapping is found.
--
-- If the dry run flag is passed, it won't link anything
--
-- NOTE: If the symbolic link already exists or there is no mapping in for the
-- given file, this does nothing.
matchAndLink :: (MonadIO io) => (Bool, Bool) -> MappedDots -> FilePath -> io ()
matchAndLink (v, dryRun) mapped dotfile = do
let dotfileAsText = asText . T.filename $ dotfile
targetM = M.lookup dotfileAsText mapped
maybe (unmatchFile $ asText dotfile) matchedFiles targetM
where
unmatchFile df = vEcho $ "No match found for dotfile " <> df <> ". Skipping..."
matchedFiles targets = forM_ targets $ \target -> do
targetExists <- (||) <$> T.testfile target <*> T.testdir target
if targetExists
then vEcho $ asText dotfile <> " already exists. Skipping..."
else do
realdotfile <- T.realpath dotfile
ensurePathExist target
T.echo $ "Linking " <> asText realdotfile <> " -> " <> asText target
unless dryRun $ lns realdotfile target
where
ensurePathExist = T.mktree . T.directory
vEcho = when v . T.echo
-- | Expand any enviroment variables found in the path
expandPath :: MonadIO io => FilePath -> io FilePath
expandPath path = do
parts <- either (T.die . pack) (traverse expand) $ parseOnly envParser (encode path)
return $ foldl' (\a x -> a </> fromText x) "/" parts
where
expand (Lit p) = return $ decodeUtf8 p
expand (Env e) =
let dieMsg = decodeUtf8 $ "Enviroment " <> e <> " not set"
in maybe (T.die dieMsg) return =<< T.need (decodeUtf8 e)
-- | Create a symbolic link (ln -s)
lns :: MonadIO io => FilePath -> FilePath -> io ()
lns src target = liftIO $ createSymbolicLink (toText' src) (toText' target)
where toText' = unpack . asText
-- Utils ----------------------------------------------------------------------
-- TODO:pjrt this is lazy and probably wrong
asText :: FilePath -> Text
asText = either id id . toText