mirror of
https://github.com/danieljharvey/tmux-mate.git
synced 2024-08-16 03:30:22 +03:00
Great
This commit is contained in:
parent
5194824efe
commit
6d3d7f2bee
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
.DS_Store
|
||||
.stack-work/
|
||||
tmux-mate.cabal
|
||||
*~
|
50
README.md
50
README.md
@ -2,23 +2,53 @@
|
||||
|
||||
Manage your tmux sessions with the delicious power of Dhall.
|
||||
|
||||
### Trying it
|
||||
### Motivation
|
||||
|
||||
To use:
|
||||
Working on modern microservice architectures usually means spinning up various combinations of 5 or more different services. Remembering what they are is a totally `1x` use of your time, let's automate it!
|
||||
|
||||
`git clone https://github.com/danieljharvey/tmux-mate`
|
||||
### Getting started
|
||||
|
||||
`cd tmux-mate`
|
||||
```bash
|
||||
# clone this repo
|
||||
git clone https://github.com/danieljharvey/tmux-mate`
|
||||
|
||||
`stack install`
|
||||
# enter the blessed folder
|
||||
cd tmux-mate
|
||||
|
||||
`export TMUX_MATE_PATH='./test/samples/Sample1.dhall && tmux-mate`
|
||||
# install tmux-mate using Haskell Stack (install instructions here: https://docs.haskellstack.org/en/stable/install_and_upgrade/)
|
||||
# this will put tmux-mate-exe in your path
|
||||
stack install
|
||||
|
||||
You should now see some garbage and your session.
|
||||
# curse this terrible env var based API for passing config files and run tmux-mate
|
||||
export TMUX_MATE_PATH='./samples/Sample1.dhall && tmux-mate-exe
|
||||
```
|
||||
|
||||
### Making your own dhall files
|
||||
You should now see a `tmux` window running two infinite loops (that will soon wear your battery down, apologies). What if it turns out we need more things in our development environment?
|
||||
|
||||
Look in `test/samples` for ideas.
|
||||
```bash
|
||||
# Run tmux-mate with the second sample script
|
||||
export TMUX_MATE_PATH='./samples/Sample2.dhall && tmux-mate-exe
|
||||
```
|
||||
|
||||
You will now see your same session with an extra window added. `tmux-mate` has diffed the two sessions and added/removed the changes. This might seem like a useless optimization when running a trivial process like `yes`, but when running multiple build environments this saves loads of time.
|
||||
|
||||
### Configuration
|
||||
|
||||
This project uses [Dhall](https://dhall-lang.org/) files for configuration. There are some examples in the `/samples/` folders that demonstrate how to put one together. This is the schema:
|
||||
|
||||
```
|
||||
{ sessionTitle : Text
|
||||
, sessionWindows : List
|
||||
{ windowTitle : Text
|
||||
, windowPanes : List { paneCommand : Text }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A few rules
|
||||
|
||||
- All of the `sessionTitle` and `windowTitle` entries must be non-empty - they are used to manage the sessions internally.
|
||||
- The session must contain at least one window, and each window must contain at least one pane.
|
||||
|
||||
### Requirements
|
||||
|
||||
@ -27,4 +57,4 @@ You will need a recent version of `tmux` installed. I tested on version 3, but I
|
||||
### Prior art
|
||||
|
||||
Very much inspired by [Tmuxinator](https://github.com/tmuxinator/tmuxinator), a
|
||||
great project that doesn't *quite* do what I needed.
|
||||
great project that doesn't _quite_ do what I needed.
|
||||
|
11
app/Main.hs
11
app/Main.hs
@ -1,11 +1,18 @@
|
||||
module Main where
|
||||
|
||||
import System.Environment
|
||||
import System.Exit
|
||||
import TmuxMate
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
path <- lookupEnv "TMUX_MATE_PATH"
|
||||
case path of
|
||||
Just dhallPath -> loadTestSession dhallPath
|
||||
Nothing -> putStrLn "Pass a valid path to TMUX_MATE_PATH pls"
|
||||
Just dhallPath -> do
|
||||
didItWork <- loadTestSession dhallPath
|
||||
case didItWork of
|
||||
Yeah -> exitWith ExitSuccess
|
||||
Nah i -> exitWith (ExitFailure i)
|
||||
Nothing -> do
|
||||
putStrLn "Pass a valid path to TMUX_MATE_PATH pls"
|
||||
exitWith (ExitFailure 1)
|
||||
|
@ -54,3 +54,4 @@ tests:
|
||||
- generic-arbitrary
|
||||
- dhall
|
||||
- hspec
|
||||
- text
|
||||
|
8
samples/MissingTitle.dhall
Normal file
8
samples/MissingTitle.dhall
Normal file
@ -0,0 +1,8 @@
|
||||
{ sessionTitle = ""
|
||||
, sessionWindows =
|
||||
[ { windowTitle = "first-window"
|
||||
, windowPanes =
|
||||
[ { paneCommand = "yes 'Pane 1'" }, { paneCommand = "yes 'Pane 2'" } ]
|
||||
}
|
||||
]
|
||||
}
|
8
samples/MissingWindowTitle.dhall
Normal file
8
samples/MissingWindowTitle.dhall
Normal file
@ -0,0 +1,8 @@
|
||||
{ sessionTitle = "foo"
|
||||
, sessionWindows =
|
||||
[ { windowTitle = ""
|
||||
, windowPanes =
|
||||
[ { paneCommand = "yes 'Pane 1'" }, { paneCommand = "yes 'Pane 2'" } ]
|
||||
}
|
||||
]
|
||||
}
|
7
samples/NoWindowPanes.dhall
Normal file
7
samples/NoWindowPanes.dhall
Normal file
@ -0,0 +1,7 @@
|
||||
{ sessionTitle = "foo"
|
||||
, sessionWindows =
|
||||
[ { windowTitle = "first-window"
|
||||
, windowPanes = [] : List { paneCommand : Text }
|
||||
}
|
||||
]
|
||||
}
|
8
samples/Sample1.dhall
Normal file
8
samples/Sample1.dhall
Normal file
@ -0,0 +1,8 @@
|
||||
{ sessionTitle = "foo"
|
||||
, sessionWindows =
|
||||
[ { windowTitle = "first-window"
|
||||
, windowPanes =
|
||||
[ { paneCommand = "yes 'Pane 1'" }, { paneCommand = "yes 'Pane 2'" } ]
|
||||
}
|
||||
]
|
||||
}
|
14
samples/Sample2.dhall
Normal file
14
samples/Sample2.dhall
Normal file
@ -0,0 +1,14 @@
|
||||
-- here we are taking our first Dhall file and adding another window to it
|
||||
let sample1 = ./Sample1.dhall
|
||||
|
||||
in { sessionTitle = sample1.sessionTitle
|
||||
, sessionWindows =
|
||||
sample1.sessionWindows
|
||||
# [ { windowTitle = "second-window"
|
||||
, windowPanes =
|
||||
[ { paneCommand = "yes 'Pane 3'" }
|
||||
, { paneCommand = "yes 'Pane 4'" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
4
samples/Schema.dhall
Normal file
4
samples/Schema.dhall
Normal file
@ -0,0 +1,4 @@
|
||||
{ sessionTitle : Text
|
||||
, sessionWindows :
|
||||
List { windowTitle : Text, windowPanes : List { paneCommand : Text } }
|
||||
}
|
@ -3,14 +3,10 @@
|
||||
|
||||
module TmuxMate
|
||||
( loadTestSession,
|
||||
DidItWork (..),
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Exception
|
||||
import Data.List (nub)
|
||||
import qualified Data.List.NonEmpty as NE
|
||||
import Data.Maybe
|
||||
import Data.Monoid (Any (..))
|
||||
import qualified Dhall as Dhall
|
||||
import System.Process
|
||||
import TmuxMate.Commands
|
||||
@ -29,46 +25,27 @@ runCommands =
|
||||
( \(Command a) -> callCommand a
|
||||
)
|
||||
|
||||
testSession :: Session
|
||||
testSession =
|
||||
Session
|
||||
{ sessionTitle = SessionName "foo",
|
||||
sessionWindows =
|
||||
[ Window
|
||||
{ windowTitle = WindowName "first-window",
|
||||
windowPanes =
|
||||
[ Pane (PaneCommand "yes 'Pane 1'"),
|
||||
Pane (PaneCommand "yes 'Pane 2'"),
|
||||
Pane (PaneCommand "yes 'Pane 3'"),
|
||||
Pane (PaneCommand "yes 'Pane 4'")
|
||||
]
|
||||
},
|
||||
Window
|
||||
{ windowTitle = WindowName "second-window",
|
||||
windowPanes =
|
||||
[ Pane (PaneCommand "yes 'Second Window - Pane 1'"),
|
||||
Pane (PaneCommand "yes 'Second Window - Pane 2'"),
|
||||
Pane (PaneCommand "yes 'Second Window - Pane 3'"),
|
||||
Pane (PaneCommand "yes 'Second Window - Pane 4'")
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
data DidItWork
|
||||
= Yeah
|
||||
| Nah Int
|
||||
|
||||
loadTestSession :: FilePath -> IO ()
|
||||
loadTestSession :: FilePath -> IO DidItWork
|
||||
loadTestSession path = do
|
||||
--let (decoder :: Dhall.Decoder Session) = Dhall.auto
|
||||
--- config <- Dhall.inputFile decoder path
|
||||
let config = testSession
|
||||
let (decoder :: Dhall.Decoder Session) = Dhall.auto
|
||||
config <- Dhall.inputFile decoder path
|
||||
case parseSession config of
|
||||
Left e -> print e
|
||||
Left e -> do
|
||||
putStrLn $ "Error parsing config at " <> path
|
||||
print e
|
||||
pure (Nah 1)
|
||||
Right config' -> do
|
||||
tmuxState <- askTmuxState
|
||||
print tmuxState
|
||||
-- print tmuxState
|
||||
let tmuxCommands = getTmuxCommands config' tmuxState
|
||||
putStrLn "Tmux Commands"
|
||||
print tmuxCommands
|
||||
-- putStrLn "Tmux Commands"
|
||||
-- print tmuxCommands
|
||||
let commands = getCommands tmuxCommands
|
||||
putStrLn "Shell commands"
|
||||
print commands
|
||||
-- putStrLn "Shell commands"
|
||||
-- print commands
|
||||
runCommands commands
|
||||
pure Yeah
|
||||
|
@ -10,7 +10,8 @@ sendKeys (VSessionName name) str =
|
||||
<> str
|
||||
<> "\" ENTER"
|
||||
|
||||
--
|
||||
adminPaneName :: String
|
||||
adminPaneName = "tmux-mate-admin"
|
||||
|
||||
-- turns our DSL into actual tmux commands
|
||||
createActualCommand :: TmuxCommand -> [Command]
|
||||
@ -19,22 +20,22 @@ createActualCommand (CreateAdminPane (VSessionName seshName)) =
|
||||
"tmux split-window -v -t "
|
||||
<> NE.toList seshName
|
||||
createActualCommand (KillAdminPane seshName) =
|
||||
[ Command $ "tmux select-window -t tmux-mate-admin",
|
||||
[ Command $ "tmux select-window -t " <> adminPaneName,
|
||||
sendKeys seshName "exit"
|
||||
]
|
||||
createActualCommand (CreatePane seshName (VWindowName winName) cmd) =
|
||||
createActualCommand (CreatePane _ (VWindowName winName) newCmd) =
|
||||
[ Command $ "tmux select-window -t " <> NE.toList winName,
|
||||
Command $
|
||||
"tmux split-window "
|
||||
<> (getCommand cmd),
|
||||
<> (getCommand newCmd),
|
||||
Command $ "tmux select-layout even-horizontal" -- for now let's stop it filling up
|
||||
]
|
||||
createActualCommand (KillPane seshName index) =
|
||||
createActualCommand (KillPane seshName paneIndex) =
|
||||
pure $
|
||||
sendKeys
|
||||
seshName
|
||||
( "tmux kill-pane -t "
|
||||
<> show index
|
||||
<> show paneIndex
|
||||
)
|
||||
createActualCommand (AttachToSession (VSessionName seshName)) =
|
||||
pure $ Command $
|
||||
@ -48,10 +49,17 @@ createActualCommand (NewSession (VSessionName seshName)) =
|
||||
pure $ Command $
|
||||
"tmux new-session -d -s "
|
||||
<> NE.toList seshName
|
||||
<> " -n tmux-mate-admin"
|
||||
createActualCommand (CreateWindow (VSessionName seshName) (VWindowName winName) (Command cmd)) =
|
||||
pure $ Command $
|
||||
"tmux new-window -n "
|
||||
<> NE.toList winName
|
||||
<> " "
|
||||
<> cmd
|
||||
<> " -n "
|
||||
<> adminPaneName
|
||||
createActualCommand (CreateWindow _ (VWindowName winName) (Command newCmd)) =
|
||||
[ Command $
|
||||
"tmux new-window -n "
|
||||
<> NE.toList winName
|
||||
<> " "
|
||||
<> newCmd
|
||||
]
|
||||
createActualCommand (KillWindow _ (VWindowName winName)) =
|
||||
[ Command $
|
||||
"tmux kill-window -t "
|
||||
<> NE.toList winName
|
||||
]
|
||||
|
@ -3,8 +3,8 @@
|
||||
module TmuxMate.Running where
|
||||
|
||||
import Control.Exception
|
||||
import Data.List (intercalate, isPrefixOf)
|
||||
import Data.Maybe (catMaybes, listToMaybe)
|
||||
import Data.List (intercalate)
|
||||
import Data.Maybe (catMaybes)
|
||||
import System.Environment
|
||||
import System.Process
|
||||
import Text.Read
|
||||
@ -13,16 +13,16 @@ import TmuxMate.Validate
|
||||
|
||||
buildTmuxState :: IO TmuxState
|
||||
buildTmuxState = do
|
||||
sessions <- askRunningSessions
|
||||
running <- askRunning
|
||||
sessions' <- askRunningSessions
|
||||
running' <- askRunning
|
||||
inTmux <- askIfWeAreInTmux
|
||||
pure $ TmuxState inTmux running sessions
|
||||
pure $ TmuxState inTmux running' sessions'
|
||||
|
||||
askTmuxState :: IO TmuxState
|
||||
askTmuxState =
|
||||
catch
|
||||
(buildTmuxState)
|
||||
(\(e :: IOError) -> pure def)
|
||||
(\(_ :: IOError) -> pure def)
|
||||
where
|
||||
def = TmuxState
|
||||
{ inSession = NotInTmuxSession,
|
||||
@ -34,7 +34,7 @@ askTmuxState =
|
||||
|
||||
askRunning :: IO [Running]
|
||||
askRunning = do
|
||||
str <- catch readTmuxProcess (\(e :: IOError) -> pure "")
|
||||
str <- catch readTmuxProcess (\(_ :: IOError) -> pure "")
|
||||
pure $ parseRunning str
|
||||
|
||||
-- ask Tmux what's cooking
|
||||
@ -47,7 +47,7 @@ readTmuxProcess =
|
||||
-- "foo/npoo/n0/n"
|
||||
askRunningSessions :: IO [VSessionName]
|
||||
askRunningSessions = do
|
||||
str <- catch readTmuxSessions (\(e :: IOError) -> pure "")
|
||||
str <- catch readTmuxSessions (\(_ :: IOError) -> pure "")
|
||||
pure $ catMaybes $
|
||||
( hush
|
||||
. parseSessionName
|
||||
@ -70,7 +70,7 @@ askIfWeAreInTmux = do
|
||||
case tmuxEnv of
|
||||
Nothing -> pure NotInTmuxSession
|
||||
Just "" -> pure NotInTmuxSession
|
||||
Just a -> do
|
||||
Just _ -> do
|
||||
case (parseSessionName seshName) of
|
||||
Right seshName' -> pure $ InTmuxSession seshName'
|
||||
_ -> pure NotInTmuxSession
|
||||
@ -104,20 +104,20 @@ parseSingle :: String -> Maybe Running
|
||||
parseSingle str =
|
||||
Running
|
||||
<$> seshName
|
||||
<*> windowName
|
||||
<*> cmd
|
||||
<*> index
|
||||
<*> windowName'
|
||||
<*> cmd'
|
||||
<*> index'
|
||||
where
|
||||
seshName =
|
||||
(SessionName <$> myLookup 0 subStrs)
|
||||
>>= (hush . parseSessionName)
|
||||
windowName =
|
||||
windowName' =
|
||||
(WindowName <$> myLookup 1 subStrs)
|
||||
>>= (hush . parseWindowName)
|
||||
index =
|
||||
index' =
|
||||
myLookup 2 subStrs
|
||||
>>= readMaybe
|
||||
cmd = case intercalate ":" (drop 3 subStrs) of
|
||||
cmd' = case intercalate ":" (drop 3 subStrs) of
|
||||
"" -> Nothing
|
||||
a -> Just (PaneCommand a)
|
||||
subStrs = wordsWhen (== ':') str
|
||||
|
@ -10,17 +10,11 @@ module TmuxMate.TmuxCommands
|
||||
)
|
||||
where
|
||||
|
||||
import Control.Exception
|
||||
import Data.List (nub)
|
||||
import qualified Data.List.NonEmpty as NE
|
||||
import Data.Maybe
|
||||
import Data.Monoid (Any (..))
|
||||
import qualified Dhall as Dhall
|
||||
import System.Process
|
||||
import TmuxMate.Commands
|
||||
import TmuxMate.Running
|
||||
import TmuxMate.Types
|
||||
import TmuxMate.Validate
|
||||
|
||||
getTmuxCommands :: ValidatedSession -> TmuxState -> [TmuxCommand]
|
||||
getTmuxCommands sesh tmuxState =
|
||||
@ -36,46 +30,40 @@ getTmuxCommands sesh tmuxState =
|
||||
InTmuxSession sesh' -> sesh'
|
||||
sWindows =
|
||||
NE.toList (vSessionWindows sesh)
|
||||
in {-case runningInTmux of
|
||||
NotInTmuxSession -> NE.tail (vSessionWindows sesh) -- first one is dealt with in invocation of session
|
||||
InTmuxSession _ -> NE.toList (vSessionWindows sesh)-}
|
||||
(createSession runningInTmux sesh runningSessions)
|
||||
in (createSession runningInTmux sesh runningSessions)
|
||||
<> ( concatMap
|
||||
(createWindow sTitle runningPanes)
|
||||
sWindows
|
||||
)
|
||||
<> (removeWindowPanes sTitle runningPanes sWindows)
|
||||
<> (removeWindows sTitle runningPanes sWindows)
|
||||
<> (removeAdminPane sTitle)
|
||||
<> ( if needsNewSession runningInTmux sTitle runningSessions
|
||||
then removeAdminPane sTitle
|
||||
else []
|
||||
)
|
||||
<> [AttachToSession sTitle]
|
||||
|
||||
-- create a new session if required
|
||||
createSession :: InTmuxSession -> ValidatedSession -> [VSessionName] -> [TmuxCommand]
|
||||
createSession inTmux session _runningSesh =
|
||||
let seshName = vSessionTitle session
|
||||
in case inTmux of
|
||||
InTmuxSession currentSesh -> [] -- AttachToSession currentSesh]
|
||||
NotInTmuxSession ->
|
||||
if sessionExists seshName _runningSesh
|
||||
then [AttachToSession seshName]
|
||||
else
|
||||
[ NewSession
|
||||
seshName
|
||||
]
|
||||
createSession inTmux session runningSesh =
|
||||
if needsNewSession inTmux (vSessionTitle session) runningSesh
|
||||
then [NewSession (vSessionTitle session)]
|
||||
else []
|
||||
|
||||
sessionExists :: VSessionName -> [VSessionName] -> Bool
|
||||
sessionExists = elem
|
||||
needsNewSession :: InTmuxSession -> VSessionName -> [VSessionName] -> Bool
|
||||
needsNewSession NotInTmuxSession seshName runningSesh = not (elem seshName runningSesh)
|
||||
needsNewSession _ _ _ = False
|
||||
|
||||
-- do we need to create this window?
|
||||
createWindow :: VSessionName -> [Running] -> VWindow -> [TmuxCommand]
|
||||
createWindow seshName running window =
|
||||
if windowExists seshName (vWindowTitle window) running
|
||||
createWindow seshName running' window =
|
||||
if windowExists seshName (vWindowTitle window) running'
|
||||
then
|
||||
createWindowPanes
|
||||
seshName
|
||||
(vWindowTitle window)
|
||||
(NE.toList $ vWindowPanes window)
|
||||
running
|
||||
running'
|
||||
else
|
||||
pure
|
||||
( CreateWindow
|
||||
@ -87,25 +75,33 @@ createWindow seshName running window =
|
||||
seshName
|
||||
(vWindowTitle window)
|
||||
(NE.tail $ vWindowPanes window)
|
||||
running
|
||||
running'
|
||||
|
||||
windowExists :: VSessionName -> VWindowName -> [Running] -> Bool
|
||||
windowExists seshName winName running =
|
||||
length (filter (\a -> windowName a == winName && sessionName a == seshName) running) > 0
|
||||
windowExists seshName winName running' =
|
||||
length
|
||||
( filter
|
||||
( \a ->
|
||||
windowName a == winName
|
||||
&& sessionName a == seshName
|
||||
)
|
||||
running'
|
||||
)
|
||||
> 0
|
||||
|
||||
-- create panes we need for a given window
|
||||
createWindowPanes :: VSessionName -> VWindowName -> [Pane] -> [Running] -> [TmuxCommand]
|
||||
createWindowPanes seshName windowName panes running =
|
||||
createWindowPanes seshName windowName' panes running' =
|
||||
( \pane ->
|
||||
CreatePane
|
||||
seshName
|
||||
windowName
|
||||
windowName'
|
||||
(paneCmdToCmd pane)
|
||||
)
|
||||
<$> filterPanes
|
||||
seshName
|
||||
windowName
|
||||
running
|
||||
windowName'
|
||||
running'
|
||||
panes
|
||||
|
||||
paneCmdToCmd :: Pane -> Command
|
||||
@ -114,7 +110,7 @@ paneCmdToCmd =
|
||||
|
||||
-- work out what panes we need to create
|
||||
filterPanes :: VSessionName -> VWindowName -> [Running] -> [Pane] -> [Pane]
|
||||
filterPanes seshName winName running panes =
|
||||
filterPanes seshName winName running' panes =
|
||||
filter (\pane -> not $ matchCommand (removeQuotes (paneCommand pane))) panes
|
||||
where
|
||||
matchCommand str =
|
||||
@ -125,7 +121,7 @@ filterPanes seshName winName running panes =
|
||||
&& seshName == seshName'
|
||||
&& winName == winName'
|
||||
)
|
||||
running
|
||||
running'
|
||||
)
|
||||
> 0
|
||||
|
||||
@ -133,23 +129,23 @@ filterPanes seshName winName running panes =
|
||||
-- removing stuff again
|
||||
|
||||
removeWindowPanes :: VSessionName -> [Running] -> [VWindow] -> [TmuxCommand]
|
||||
removeWindowPanes seshName running windows =
|
||||
removeWindowPanes seshName running' windows =
|
||||
(\(Running _ _ _ i) -> KillPane seshName i)
|
||||
<$> (filterRunning seshName windows running)
|
||||
<$> (filterRunning seshName windows running')
|
||||
|
||||
filterRunning :: VSessionName -> [VWindow] -> [Running] -> [Running]
|
||||
filterRunning seshName windows running =
|
||||
filterRunning seshName windows running' =
|
||||
filter
|
||||
( \(Running seshName' winName' run _) ->
|
||||
( \(Running seshName' _ run _) ->
|
||||
not $
|
||||
anyMatch (removeQuotes run) windows
|
||||
&& seshName == seshName'
|
||||
)
|
||||
running
|
||||
running'
|
||||
where
|
||||
anyMatch :: PaneCommand -> [VWindow] -> Bool
|
||||
anyMatch str windows' =
|
||||
getAny (foldMap (matchCommand str) windows)
|
||||
getAny (foldMap (matchCommand str) windows')
|
||||
matchCommand :: PaneCommand -> VWindow -> Any
|
||||
matchCommand str window =
|
||||
Any $
|
||||
@ -163,7 +159,7 @@ filterRunning seshName windows running =
|
||||
> 0
|
||||
|
||||
removeWindows :: VSessionName -> [Running] -> [VWindow] -> [TmuxCommand]
|
||||
removeWindows seshName running windows =
|
||||
removeWindows seshName running' windows =
|
||||
( \winTitle' ->
|
||||
KillWindow
|
||||
seshName
|
||||
@ -178,7 +174,14 @@ removeWindows seshName running windows =
|
||||
requiredWindowNames =
|
||||
vWindowTitle <$> windows
|
||||
runningWindowNames =
|
||||
nub $ windowName <$> filter (\(Running sesh' win' _ _) -> sesh' == seshName) running
|
||||
nub $
|
||||
windowName
|
||||
<$> filter
|
||||
( \(Running sesh' _ _ _) ->
|
||||
sesh'
|
||||
== seshName
|
||||
)
|
||||
running'
|
||||
|
||||
-- remove admin window (always)
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
module TmuxMate.Types where
|
||||
|
||||
import Data.List.NonEmpty
|
||||
import Dhall (Decoder, FromDhall, ToDhall, autoWith)
|
||||
import Dhall (FromDhall, ToDhall)
|
||||
import GHC.Generics
|
||||
|
||||
data InTmuxSession
|
||||
@ -71,7 +71,6 @@ data TmuxCommand
|
||||
| AttachToSession VSessionName
|
||||
| KillSession VSessionName
|
||||
| NewSession VSessionName
|
||||
| SendKeys VSessionName String
|
||||
deriving (Eq, Ord, Show, Generic)
|
||||
|
||||
newtype Command
|
||||
@ -93,7 +92,16 @@ data ValidationError
|
||||
| NoWindows
|
||||
| EmptyWindowName
|
||||
| WindowWithNoPanes VWindowName
|
||||
deriving (Eq, Ord, Show)
|
||||
deriving (Eq, Ord)
|
||||
|
||||
instance Show ValidationError where
|
||||
show EmptySessionName = "Session title must not be an empty string."
|
||||
show NoWindows = "Session must contain at least one window."
|
||||
show EmptyWindowName = "All windows must have a non-empty title."
|
||||
show (WindowWithNoPanes (VWindowName name)) =
|
||||
"Window '"
|
||||
<> toList name
|
||||
<> "' does not have any panes! All windows must contain at least one pane."
|
||||
|
||||
newtype VSessionName
|
||||
= VSessionName {getVSessionName :: NonEmpty Char}
|
||||
|
@ -47,6 +47,10 @@ extra-deps:
|
||||
# Override default flag values for local packages and extra-deps
|
||||
# flags: {}
|
||||
|
||||
ghc-options:
|
||||
# All packages
|
||||
"$locals": -Wall
|
||||
|
||||
# Extra package databases containing global packages
|
||||
# extra-package-dbs: []
|
||||
|
||||
|
15
test/Spec.hs
15
test/Spec.hs
@ -1,6 +1,9 @@
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE TypeApplications #-}
|
||||
|
||||
import qualified Data.List.NonEmpty as NE
|
||||
import qualified Data.Text as Text
|
||||
import qualified Data.Text.IO as Text.IO
|
||||
import Dhall
|
||||
import Dhall.Core (pretty)
|
||||
import Test.Hspec
|
||||
@ -9,7 +12,6 @@ import Test.QuickCheck.Monadic
|
||||
import qualified Tests.TmuxMate.TmuxCommands as TmuxCommands
|
||||
import Tests.TmuxMate.Types (Session)
|
||||
import qualified Tests.TmuxMate.Validate as Validate
|
||||
import TmuxMate
|
||||
import TmuxMate.Running
|
||||
import TmuxMate.Types
|
||||
|
||||
@ -52,10 +54,13 @@ main = hspec $ do
|
||||
(PaneCommand "yes Pane 1")
|
||||
1
|
||||
]
|
||||
|
||||
{-describe "Dhall" $ do
|
||||
it "Round trips Dhall encoding" $ do
|
||||
property dhallSessionRoundtrip -}
|
||||
describe "Dhall" $ do
|
||||
it "Round trips Dhall encoding" $ do
|
||||
property dhallSessionRoundtrip
|
||||
it "Generates a Dhall schema that matches our advertised one" $ do
|
||||
let schema = (Dhall.Core.pretty (Dhall.expected (Dhall.auto @Session)))
|
||||
savedSchema <- Text.IO.readFile "./samples/Schema.dhall"
|
||||
Text.stripEnd schema `shouldBe` Text.stripEnd savedSchema
|
||||
|
||||
dhallSessionRoundtrip :: Property
|
||||
dhallSessionRoundtrip =
|
||||
|
@ -4,12 +4,6 @@ module Tests.TmuxMate.TmuxCommands where
|
||||
|
||||
import qualified Data.List.NonEmpty as NE
|
||||
import Test.Hspec
|
||||
import Test.QuickCheck
|
||||
import Test.QuickCheck.Monadic
|
||||
import Tests.TmuxMate.Types (Session)
|
||||
import qualified Tests.TmuxMate.Validate as Validate
|
||||
import TmuxMate
|
||||
import TmuxMate.Running
|
||||
import TmuxMate.TmuxCommands
|
||||
import TmuxMate.Types
|
||||
|
||||
@ -36,12 +30,12 @@ spec = do
|
||||
sampleSession
|
||||
[]
|
||||
`shouldBe` []
|
||||
it "Attaches to session if it already exists" $ do
|
||||
it "Does nothing if session already exists" $ do
|
||||
createSession
|
||||
NotInTmuxSession
|
||||
sampleSession
|
||||
[VSessionName $ NE.fromList "horses"]
|
||||
`shouldBe` [AttachToSession (VSessionName $ NE.fromList "horses")]
|
||||
`shouldBe` [] -- AttachToSession (VSessionName $ NE.fromList "horses")]
|
||||
it "Creates a session if we are not in tmux and session is not running" $ do
|
||||
createSession NotInTmuxSession sampleSession []
|
||||
`shouldBe` [NewSession (VSessionName $ NE.fromList "horses")]
|
||||
|
@ -2,6 +2,7 @@
|
||||
{-# LANGUAGE DerivingVia #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE StandaloneDeriving #-}
|
||||
{-# OPTIONS_GHC -fno-warn-orphans #-}
|
||||
|
||||
module Tests.TmuxMate.Types
|
||||
( Session,
|
||||
|
@ -1,14 +1,7 @@
|
||||
module Tests.TmuxMate.Validate where
|
||||
|
||||
import qualified Data.List.NonEmpty as NE
|
||||
import Dhall
|
||||
import Dhall.Core (pretty)
|
||||
import Test.Hspec
|
||||
import Test.QuickCheck
|
||||
import Test.QuickCheck.Monadic
|
||||
import Tests.TmuxMate.Types (Session)
|
||||
import TmuxMate
|
||||
import TmuxMate.Running
|
||||
import TmuxMate.Types
|
||||
import TmuxMate.Validate
|
||||
|
||||
|
@ -1,4 +0,0 @@
|
||||
{ sessionTitle = "foo"
|
||||
, sessionPanes =
|
||||
[ { paneCommand = "yes 'Pane 1'" }, { paneCommand = "yes 'Pane 2'" } ]
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
-- here we are taking our first Dhall file and adding another item to it
|
||||
let sample1 = ./Sample1.dhall
|
||||
|
||||
in { sessionTitle = sample1.sessionTitle
|
||||
, sessionPanes = sample1.sessionPanes # [ { paneCommand = "yes 'Pane 3'" } ]
|
||||
}
|
Loading…
Reference in New Issue
Block a user