Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions extras/completion/bash
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
_maid() {
case "$2","$3" in
-*,*)
COMPREPLY=( $(compgen -W "-h -l -n -q -f --help --list --dry-run --quiet --taskfile" -- "$2") )
COMPREPLY=( $(compgen -W "-h -l -n -q -f -w -r --help --list --dry-run --quiet --taskfile --watch --restart" -- "$2") )
;;
*,-f | *,--maidfile)
*,-f | *,--maidfile | *,-w | *,--watch)
COMPREPLY=( $(compgen -f -- "$2") )
;;
*)
Expand Down
7 changes: 5 additions & 2 deletions extras/completion/fish
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
complete -c maid -n "__fish_is_first_arg" -f
complete -c maid -e
complete -c maid -n "__fish_is_first_token" -f
complete -c maid -a "(maid -l | sed 's/ \(.\)/\t\L\1/')"
complete -c maid -s h -l help -d "display usage"
complete -c maid -s l -l list -d "list tasks concisely"
complete -c maid -s n -l dry-run -d "don't run anything, only display commands"
complete -c maid -s q -l quiet -d "don't display anything"
complete -c maid -s f -l taskfile -d "use tasks in file"
complete -c maid -s f -l taskfile -r -d "use tasks in file"
complete -c maid -s w -l watch -r -d "watch files in path"
complete -c maid -s r -l restart -d "restart tasks instead of waiting"

# vim: ft=fish
2 changes: 2 additions & 0 deletions extras/completion/zsh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ _maid() {
"(dry-run)"{-n,--dry-run}"[don't run anything, only display commands]" \
"(quiet)"{-q,--quiet}"[don't display anything]" \
"(taskfile)"{-f,--taskfile=}"[use tasks in file]:maidfile:_files" \
"(watch)"{-w,--watch=}"[watch tasks in path]:watch:_files" \
"(restart)"{-r,--restart}"[restart tasks instead of waiting]" \
":task:_maid_tasks" \
"*:args:_default"
}
Expand Down
9 changes: 5 additions & 4 deletions maid-build.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ source-repository head
location: https://github.com/rniii/maid.git

common base
ghc-options: -Wall -Wno-name-shadowing
ghc-options: -Wall -Wno-name-shadowing -threaded
default-language: Haskell2010
build-depends: base >= 4.7 && <5

Expand All @@ -31,13 +31,14 @@ library
Maid.Parser,
build-depends:
ansi-terminal ^>= 1.1,
bytestring ^>= 0.11,
directory ^>= 1.3,
filepath ^>= 1.5,
process ^>= 1.6,
bytestring ^>= 0.11,
text ^>= 2.0,
fsnotify ^>= 0.4,
mtl ^>= 2.2,
process ^>= 1.6,
temporary ^>= 1.3,
text ^>= 2.0,

executable maid
import: base
Expand Down
73 changes: 65 additions & 8 deletions src/Maid.hs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ module Maid (run, MaidError) where
import Maid.Parser (Task (..), parseTasks)

import Control.Applicative (liftA2)
import Control.Concurrent (forkIO, killThread, newEmptyMVar, newMVar, putMVar, swapMVar, takeMVar, threadDelay, tryTakeMVar)
import Control.Exception (Exception, handle, throw)
import Control.Monad (forM_, unless, when)
import Control.Monad (forM_, forever, unless, void, when)
import Control.Monad.Reader (MonadIO (liftIO), MonadReader, ReaderT (runReaderT), asks)
import Control.Monad.State (StateT, execStateT, gets, modify)
import Data.Bool (bool)
Expand All @@ -23,6 +24,7 @@ import System.Console.GetOpt (ArgDescr (NoArg, ReqArg), ArgOrder (RequireOrder),
import System.Directory (getCurrentDirectory)
import System.Environment (getArgs, getProgName, lookupEnv)
import System.Exit (ExitCode (ExitFailure), exitFailure, exitSuccess, exitWith)
import System.FSNotify (watchTree, withManager)
import System.FilePath (combine, isDrive, takeDirectory, takeFileName)
import System.IO (hClose, hPutStrLn, stderr, stdout)
import System.IO.Error (catchIOError)
Expand All @@ -47,6 +49,8 @@ run = do
, Option ['n'] ["dry-run"] (NoArg DryRun) "Don't run anything, only display commands"
, Option ['q'] ["quiet"] (NoArg Quiet) "Don't display anything"
, Option ['f'] ["taskfile"] (ReqArg Maidfile "FILE") "Use tasks in FILE"
, Option ['w'] ["watch"] (ReqArg Watch "PATH") "Watch files in PATH. Can be specified multiple times"
, Option ['r'] ["restart"] (NoArg Restart) "When files change, restart the task instead of waiting"
]

parseOpt :: Flag -> StateT Context IO ()
Expand All @@ -70,8 +74,19 @@ run = do
when (null tasks) $ throw $ NoTasks file

modify $ \c -> c{ctxTaskfile = (file, tasks)}

context = Context ([], []) False False <$> defaultStyle
parseOpt (Watch dir) = modify $ \c -> c{ctxWatch = dir : ctxWatch c}
parseOpt Restart = modify $ \c -> c{ctxWatchRestart = True}

context =
(`fmap` defaultStyle) $ \style ->
Context
{ ctxTaskfile = ([], [])
, ctxDryRun = False
, ctxQuiet = False
, ctxWatch = []
, ctxWatchRestart = False
, ctxStyle = style
}

setTaskfile ctx = case ctxTaskfile ctx of
([], []) -> do
Expand All @@ -81,10 +96,17 @@ run = do

bail e = putErr e >> exitFailure

runArgs (task : args) = runTask (T.pack task) args
runArgs (task : args) = execTask (T.pack task) args
runArgs [] = listTasks

data Flag = Help | List | DryRun | Quiet | Maidfile String
data Flag
= Help
| List
| DryRun
| Quiet
| Maidfile String
| Watch String
| Restart

putErr :: String -> IO ()
putErr msg = do
Expand All @@ -95,10 +117,43 @@ getTask :: Text -> [Task] -> Task
getTask name =
fromMaybe (throw $ UnknownTask $ T.unpack name) . find ((. tName) (== name))

runTask :: Text -> [String] -> Maid ()
runTask name args = do
execTask :: Text -> [String] -> Maid ()
execTask name args = do
task <- asks (getTask name . snd . ctxTaskfile)
displayCommand ("maid " <> name)
watch <- asks ctxWatch
watchTask watch task args

watchTask :: [FilePath] -> Task -> [String] -> Maid ()
watchTask [] task args = do
runTask task args
watchTask paths task args = do
restart <- asks ctxWatchRestart
exec <- asks $ runReaderT $ unMaid $ runTask task args
liftIO $ withManager $ \mgr -> do
action <- bool watchWait watchRestart restart exec
forM_ paths $ \path ->
watchTree mgr path (const True) (const action)
forever (threadDelay maxBound)
where
watchWait exec = do
void exec
lock <- newMVar ()
return $ void $ forkIO $ do
blocked <- isNothing <$> tryTakeMVar lock
unless blocked (exec >> putMVar lock ())
watchRestart exec = do
active <- forkIO exec >>= newMVar
-- debounced child thread, makes infinite loops less overwhelming
lock <- newEmptyMVar
void $ forkIO $ forever $ do
takeMVar lock
killThread =<< swapMVar active =<< forkIO exec
threadDelay (300 * 1000)
return $ putMVar lock ()

runTask :: Task -> [String] -> Maid ()
runTask task args = do
displayCommand ("maid " <> tName task)
runLang (tLang task) args (tCode task)

runLang :: Text -> [String] -> Text -> Maid ()
Expand Down Expand Up @@ -181,6 +236,8 @@ data Context = Context
{ ctxTaskfile :: (FilePath, [Task])
, ctxDryRun :: Bool
, ctxQuiet :: Bool
, ctxWatch :: [FilePath]
, ctxWatchRestart :: Bool
, ctxStyle :: Style
}

Expand Down