shift-0.2.1.1: src/Teleport.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Main (main) where
import Control.Composition hiding ((&))
import Control.Monad
import Data.Binary
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BSL
import Data.Functor (($>))
import Data.List
import Data.Maybe
import qualified Data.Text as T
import Data.Text.Encoding
import Data.Version
import Filesystem as P
import qualified Filesystem.Path.CurrentOS as P
import GHC.Generics
import Lens.Micro
import Options.Applicative
import Paths_shift
import Prelude hiding (FilePath)
import System.Console.ANSI
import System.Environment
import Turtle (ExitCode (ExitFailure), FilePath,
cd, die, echo, exit, fromString,
home, realpath, testdir, testfile,
unsafeTextToLine, (</>), (<>))
data AddOptions = AddOptions { folderPath :: Maybe String,
addname :: String }
newtype RemoveOptions = RemoveOptions { removename :: String }
newtype GotoOptions = GotoOptions { gotoname :: String }
-- | data type for command
data Command = Display
| Add AddOptions
| Remove RemoveOptions
| Goto GotoOptions
| Replace String
-- an abstract entity representing a point to which we can warp to
data WarpPoint = WarpPoint { _name :: String
, _absFolderPath :: String }
deriving (Generic, Binary)
-- the main data that is loaded from config
newtype WarpData = WarpData { _warpPoints :: [WarpPoint] }
deriving (Generic, Binary)
defaultWarpData :: WarpData
defaultWarpData = WarpData []
warpPoints :: Lens' WarpData [WarpPoint]
warpPoints f s = fmap (\x -> s { _warpPoints = x }) (f (_warpPoints s))
main :: IO ()
main = execParser opts >>= run
where versionInfo = infoOption ("teleport version: " ++ showVersion version) (short 'v' <> long "version" <> help "Show version")
opts = info (helper <*> versionInfo <*> parseCommand)
(fullDesc
<> progDesc "use warp to quickly setup warp points and move between them"
<> header "Warp: move around your filesystem")
decodeWarpData :: FilePath -> IO WarpData
decodeWarpData = fmap decode . (fmap BSL.fromStrict . BS.readFile) . P.encodeString
loadWarpData :: FilePath -> IO WarpData
loadWarpData configFilePath = testfile configFilePath >>= \exists ->
if exists then decodeWarpData configFilePath
else saveWarpData configFilePath defaultWarpData $> defaultWarpData
saveWarpData :: FilePath -> WarpData -> IO ()
saveWarpData configFilePath warpData =
let dataBytestring = encode warpData in
BSL.writeFile (P.encodeString configFilePath) dataBytestring
warpDataPath :: IO FilePath
warpDataPath = home >>= \homeFolder ->
pure (homeFolder </> ".warpdata")
warpnameParser :: Parser String
warpnameParser = argument str
(metavar "NAME"
<> help "name of the warp point")
parseAddCommand :: Parser Command
parseAddCommand = Add .* AddOptions <$> folderParser <*> warpnameParser
folderParser :: Parser (Maybe String)
folderParser = optional $ strOption
(long "path"
<> short 'p'
<> metavar "FOLDER"
<> help "path to the folder to warp to")
parseRemoveCommand :: Parser Command
parseRemoveCommand = Remove . RemoveOptions <$> warpnameParser
parseGotoCommand :: Parser Command
parseGotoCommand = Goto . GotoOptions <$> warpnameParser
parseReplaceCommand :: Parser Command
parseReplaceCommand = Replace <$> warpnameParser
parseCommand :: Parser Command
parseCommand = hsubparser
(command "add" (info parseAddCommand (progDesc "add a warp point"))
<> command "list" (info (pure Display) (progDesc "list all warp points"))
<> command "del" (info parseRemoveCommand (progDesc "delete a warp point"))
<> command "replace" (info parseReplaceCommand (progDesc "replace a warp point"))
<> command "to" (info parseGotoCommand (progDesc "go to a created warp point")))
setErrorColor :: IO ()
setErrorColor = setSGR [SetColor Foreground Vivid Red]
colorWhen :: IO () -> IO ()
colorWhen act = do
useColor <- fromMaybe "1" <$> lookupEnv "CLICOLOR"
if useColor /= "0" then act else mempty
warpPointPrint :: WarpPoint -> IO ()
warpPointPrint warpPoint = do
colorWhen $ setSGR [SetColor Foreground Dull White]
putStr (_name warpPoint)
colorWhen $ setSGR [SetColor Foreground Vivid Blue]
putStr $ "\t" <> _absFolderPath warpPoint <> "\n"
folderNotFoundError :: FilePath -> IO ()
folderNotFoundError path = setErrorColor >>
(die . T.pack $ ("unable to find folder: " ++ show path))
needFolderNotFileError :: FilePath -> IO ()
needFolderNotFileError path = setErrorColor >>
(die . T.pack $ "expected folder, not file: " ++ show path)
dieIfFolderNotFound :: FilePath -> IO ()
dieIfFolderNotFound path = sequence_
[ flip when (needFolderNotFileError path) =<< testfile path
, flip unless (folderNotFoundError path) =<< testdir path ]
dieWarpPointExists :: WarpPoint -> IO ()
dieWarpPointExists warpPoint = sequence_
[ setErrorColor
, putStrLn $ "warp point " <> _name warpPoint <> " already exists:\n"
, warpPointPrint warpPoint
]
runAdd :: AddOptions -> IO ()
runAdd AddOptions{..} = do
dieIfFolderNotFound . P.decode . encodeUtf8 . T.pack . fromMaybe "./" $ folderPath
putStrLn "folder exists, loading warp data..."
warpData <- loadWarpData =<< warpDataPath
_absFolderPath <- realpath . P.decode . encodeUtf8 . T.pack . fromMaybe "./" $ folderPath
let existingWarpPoint = find ((==addname) . _name) (_warpPoints warpData)
case existingWarpPoint of
Just warpPoint -> dieWarpPointExists warpPoint
Nothing -> do
putStrLn "creating warp point: \n"
let newWarpPoint = WarpPoint addname (P.encodeString _absFolderPath)
warpPointPrint newWarpPoint
let newWarpData = over warpPoints (newWarpPoint:) warpData
flip saveWarpData newWarpData =<< warpDataPath
runDisplay :: IO ()
runDisplay = do
warpData <- loadWarpData =<< warpDataPath
forM_ (_warpPoints warpData) warpPointPrint
dieWarpPointNotFound :: String ->IO ()
dieWarpPointNotFound wStr = setErrorColor >> (die . T.pack)
(wStr <> " warp point not found")
runRemove :: RemoveOptions -> IO ()
runRemove RemoveOptions{..} = do
warpPath <- warpDataPath
warp <- loadWarpData warpPath
let wantedWarpPoint = find ((/= removename) . _name) (_warpPoints warp)
case wantedWarpPoint of
Nothing -> dieWarpPointNotFound removename
Just _ -> saveWarpData warpPath
(over warpPoints (filter ((/= removename) . _name)) warp)
runGoto :: GotoOptions -> IO ()
runGoto GotoOptions{..} = do
warpPath <- warpDataPath
warp <- loadWarpData warpPath
let wantedWarpPoint = find ((== gotoname) . _name) (_warpPoints warp)
case wantedWarpPoint of
Nothing -> dieWarpPointNotFound gotoname
Just warpPoint -> do
echo (unsafeTextToLine . T.pack . _absFolderPath $ warpPoint)
cd . fromString $ _absFolderPath warpPoint
setWorkingDirectory . fromString . _absFolderPath $ warpPoint
exit (ExitFailure 2)
run :: Command -> IO ()
run (Add addOpt) = runAdd addOpt
run Display = runDisplay
run (Remove removeOpt) = runRemove removeOpt
run (Replace strR) = runRemove (RemoveOptions strR) *> runAdd (AddOptions Nothing strR)
run (Goto gotoOpt) = runGoto gotoOpt