{-# LANGUAGE PatternGuards #-}
module System.Console.CmdArgs.Explicit.Complete(
Complete(..), complete,
completeBash, completeZsh
) where
import System.Console.CmdArgs.Explicit.Type
import Control.Monad
import Data.List
import Data.Maybe
data Complete
= CompleteValue String
| CompleteFile String FilePath
| CompleteDir String FilePath
deriving (Eq,Ord)
instance Show Complete where
show (CompleteValue a) = "VALUE " ++ a
show (CompleteFile a b) = "FILE " ++ a ++ " " ++ b
show (CompleteDir a b) = "DIR " ++ a ++ " " ++ b
showList xs = showString $ unlines (map show xs)
prepend :: String -> Complete -> Complete
prepend a (CompleteFile b c) = CompleteFile (a++b) c
prepend a (CompleteDir b c) = CompleteDir (a++b) c
prepend a (CompleteValue b) = CompleteValue (a++b)
complete
:: Mode a
-> [String]
-> (Int,Int)
-> [Complete]
complete mode_ args_ (i,_) = nub $ followArgs mode args now
where
(seen,next) = splitAt i args_
now = head $ next ++ [""]
(mode,args) = followModes mode_ seen
followModes :: Mode a -> [String] -> (Mode a, [String])
followModes m (x:xs) | Just m2 <- pickBy modeNames x $ modeModes m = followModes m2 xs
followModes m xs = (m,xs)
pickBy :: (a -> [String]) -> String -> [a] -> Maybe a
pickBy f name xs = find (\x -> name `elem` f x) xs `mplus`
find (\x -> any (name `isPrefixOf`) (f x)) xs
followArgs :: Mode a -> [String] -> (String -> [Complete])
followArgs m = first
where
first [] = expectArgFlagMode (modeModes m) (argsPick 0) (modeFlags m)
first xs = norm 0 xs
norm i [] = expectArgFlag (argsPick i) (modeFlags m)
norm i ("--":xs) = expectArg $ argsPick (i + length xs)
norm i (('-':'-':x):xs) | null b, flagInfo flg == FlagReq = val i flg xs
| otherwise = norm i xs
where (a,b) = break (== '=') x
flg = getFlag a
norm i (('-':x:y):xs) = case flagInfo flg of
FlagReq | null y -> val i flg xs
| otherwise -> norm i xs
FlagOpt{} -> norm i xs
_ | "=" `isPrefixOf` y -> norm i xs
| null y -> norm i xs
| otherwise -> norm i (('-':y):xs)
where flg = getFlag [x]
norm i (x:xs) = norm (i+1) xs
val i flg [] = expectVal flg
val i flg (x:xs) = norm i xs
argsPick i = let (lst,end) = modeArgs m in if i < length lst then Just $ lst !! i else end
getFlag x = fromMaybe (flagNone [] id "") $ pickBy flagNames x $ modeFlags m
expectArgFlagMode :: [Mode a] -> Maybe (Arg a) -> [Flag a] -> String -> [Complete]
expectArgFlagMode mode arg flag x
| "-" `isPrefixOf` x = expectFlag flag x ++ [CompleteValue "-" | x == "-", isJust arg]
| otherwise = expectMode mode x ++ expectArg arg x ++ expectFlag flag x
expectArgFlag :: Maybe (Arg a) -> [Flag a] -> String -> [Complete]
expectArgFlag arg flag x
| "-" `isPrefixOf` x = expectFlag flag x ++ [CompleteValue "-" | x == "-", isJust arg]
| otherwise = expectArg arg x ++ expectFlag flag x
expectMode :: [Mode a] -> String -> [Complete]
expectMode mode = expectStrings (map modeNames mode)
expectArg :: Maybe (Arg a) -> String -> [Complete]
expectArg Nothing x = []
expectArg (Just arg) x = expectFlagHelp (argType arg) x
expectFlag :: [Flag a] -> String -> [Complete]
expectFlag flag x
| (a,_:b) <- break (== '=') x = case pickBy (map f . flagNames) a flag of
Nothing -> []
Just flg -> map (prepend (a ++ "=")) $ expectVal flg b
| otherwise = expectStrings (map (map f . flagNames) flag) x
where f x = "-" ++ ['-' | length x > 1] ++ x
expectVal :: Flag a -> String -> [Complete]
expectVal flg = expectFlagHelp (flagType flg)
expectStrings :: [[String]] -> String -> [Complete]
expectStrings xs x = map CompleteValue $ concatMap (take 1 . filter (x `isPrefixOf`)) xs
expectFlagHelp :: FlagHelp -> String -> [Complete]
expectFlagHelp typ x = case typ of
"FILE" -> [CompleteFile "" x]
"DIR" -> [CompleteDir "" x]
"FILE/DIR" -> [CompleteFile "" x, CompleteDir "" x]
"DIR/FILE" -> [CompleteDir "" x, CompleteFile "" x]
'[':s | "]" `isSuffixOf` s -> expectFlagHelp (init s) x
_ -> []
completeBash :: String -> [String]
completeBash prog =
["# Completion for " ++ prog
,"# Generated by CmdArgs: http://community.haskell.org/~ndm/cmdargs/"
,"_" ++ prog ++ "()"
,"{"
," # local CMDARGS_DEBUG=1 # uncomment to debug this script"
,""
," COMPREPLY=()"
," function add { COMPREPLY[((${#COMPREPLY[@]} + 1))]=$1 ; }"
," IFS=$'\\n\\r'"
,""
," export CMDARGS_COMPLETE=$((${COMP_CWORD} - 1))"
," result=`" ++ prog ++ " ${COMP_WORDS[@]:1}`"
,""
," if [ -n $CMDARGS_DEBUG ]; then"
," echo Call \\(${COMP_WORDS[@]:1}, $CMDARGS_COMPLETE\\) > cmdargs.tmp"
," echo $result >> cmdargs.tmp"
," fi"
," unset CMDARGS_COMPLETE"
," unset CMDARGS_COMPLETE_POS"
,""
," for x in $result ; do"
," case $x in"
," VALUE\\ *)"
," add ${x:6}"
," ;;"
," FILE\\ *)"
," local prefix=`expr match \"${x:5}\" '\\([^ ]*\\)'`"
," local match=`expr match \"${x:5}\" '[^ ]* \\(.*\\)'`"
," for x in `compgen -f -- \"$match\"`; do"
," add $prefix$x"
," done"
," ;;"
," DIR\\ *)"
," local prefix=`expr match \"${x:4}\" '\\([^ ]*\\)'`"
," local match=`expr match \"${x:4}\" '[^ ]* \\(.*\\)'`"
," for x in `compgen -d -- \"$match\"`; do"
," add $prefix$x"
," done"
," ;;"
," esac"
," done"
," unset IFS"
,""
," if [ -n $CMDARGS_DEBUG ]; then"
," echo echo COMPREPLY: ${#COMPREPLY[@]} = ${COMPREPLY[@]} >> cmdargs.tmp"
," fi"
,"}"
,"complete -o bashdefault -F _" ++ prog ++ " " ++ prog
]
completeZsh :: String -> [String]
completeZsh _ = ["echo TODO: help add Zsh completions to cmdargs programs"]