{-# LANGUAGE PatternGuards #-}-- | This module does command line completionmoduleSystem.Console.CmdArgs.Explicit.Complete(Complete(..),complete,completeBash,completeZsh)whereimportSystem.Console.CmdArgs.Explicit.TypeimportControl.MonadimportData.ListimportData.Maybe-- | How to complete a command line option.-- The 'Show' instance is suitable for parsing from shell scripts.dataComplete=CompleteValueString-- ^ Complete to a particular value|CompleteFileStringFilePath-- ^ Complete to a prefix, and a file|CompleteDirStringFilePath-- ^ Complete to a prefix, and a directoryderiving(Eq,Ord)instanceShowCompletewhereshow(CompleteValuea)="VALUE "++ashow(CompleteFileab)="FILE "++a++" "++bshow(CompleteDirab)="DIR "++a++" "++bshowListxs=showString$unlines(mapshowxs)prepend::String->Complete->Completeprependa(CompleteFilebc)=CompleteFile(a++b)cprependa(CompleteDirbc)=CompleteDir(a++b)cprependa(CompleteValueb)=CompleteValue(a++b)-- | Given a current state, return the set of commands you could type now, in preference order.complete::Modea-- ^ Mode specifying which arguments are allowed->[String]-- ^ Arguments the user has already typed->(Int,Int)-- ^ 0-based index of the argument they are currently on, and the position in that argument->[Complete]-- Roll forward looking at modes, and if you match a mode, enter it-- If the person just before is a flag without arg, look at how you can complete that arg-- If your prefix is a complete flag look how you can complete that flag-- If your prefix looks like a flag, look for legitimate flags-- Otherwise give a file/dir if they are arguments to this mode, and all flags-- If you haven't seen any args/flags then also autocomplete to any child modescompletemode_args_(i,_)=nub$followArgsmodeargsnowwhere(seen,next)=splitAtiargs_now=head$next++[""](mode,args)=followModesmode_seen-- | Given a mode and some arguments, try and drill down into the modefollowModes::Modea->[String]->(Modea,[String])followModesm(x:xs)|Justm2<-pickBymodeNamesx$modeModesm=followModesm2xsfollowModesmxs=(m,xs)pickBy::(a->[String])->String->[a]->MaybeapickByfnamexs=find(\x->name`elem`fx)xs`mplus`find(\x->any(name`isPrefixOf`)(fx))xs-- | Follow args deals with all seen arguments, then calls on to deal with the next onefollowArgs::Modea->[String]->(String->[Complete])followArgsm=firstwherefirst[]=expectArgFlagMode(modeModesm)(argsPick0)(modeFlagsm)firstxs=norm0xs-- i is the number of arguments that have gone pastnormi[]=expectArgFlag(argsPicki)(modeFlagsm)normi("--":xs)=expectArg$argsPick(i+lengthxs)normi(('-':'-':x):xs)|nullb,flagInfoflg==FlagReq=valiflgxs|otherwise=normixswhere(a,b)=break(=='=')xflg=getFlaganormi(('-':x:y):xs)=caseflagInfoflgofFlagReq|nully->valiflgxs|otherwise->normixsFlagOpt{}->normixs_|"="`isPrefixOf`y->normixs|nully->normixs|otherwise->normi(('-':y):xs)whereflg=getFlag[x]normi(x:xs)=norm(i+1)xsvaliflg[]=expectValflgvaliflg(x:xs)=normixsargsPicki=let(lst,end)=modeArgsminifi<lengthlstthenJust$lst!!ielseend-- if you can't find the flag, pick one that is FlagNone (has all the right fallback)getFlagx=fromMaybe(flagNone[]id"")$pickByflagNamesx$modeFlagsmexpectArgFlagMode::[Modea]->Maybe(Arga)->[Flaga]->String->[Complete]expectArgFlagModemodeargflagx|"-"`isPrefixOf`x=expectFlagflagx++[CompleteValue"-"|x=="-",isJustarg]|otherwise=expectModemodex++expectArgargx++expectFlagflagxexpectArgFlag::Maybe(Arga)->[Flaga]->String->[Complete]expectArgFlagargflagx|"-"`isPrefixOf`x=expectFlagflagx++[CompleteValue"-"|x=="-",isJustarg]|otherwise=expectArgargx++expectFlagflagxexpectMode::[Modea]->String->[Complete]expectModemode=expectStrings(mapmodeNamesmode)expectArg::Maybe(Arga)->String->[Complete]expectArgNothingx=[]expectArg(Justarg)x=expectFlagHelp(argTypearg)xexpectFlag::[Flaga]->String->[Complete]expectFlagflagx|(a,_:b)<-break(=='=')x=casepickBy(mapf.flagNames)aflagofNothing->[]Justflg->map(prepend(a++"="))$expectValflgb|otherwise=expectStrings(map(mapf.flagNames)flag)xwherefx="-"++['-'|lengthx>1]++xexpectVal::Flaga->String->[Complete]expectValflg=expectFlagHelp(flagTypeflg)expectStrings::[[String]]->String->[Complete]expectStringsxsx=mapCompleteValue$concatMap(take1.filter(x`isPrefixOf`))xsexpectFlagHelp::FlagHelp->String->[Complete]expectFlagHelptypx=casetypof"FILE"->[CompleteFile""x]"DIR"->[CompleteDir""x]"FILE/DIR"->[CompleteFile""x,CompleteDir""x]"DIR/FILE"->[CompleteDir""x,CompleteFile""x]'[':s|"]"`isSuffixOf`s->expectFlagHelp(inits)x_->[]----------------------------------------------------------------------- BASH SCRIPTcompleteBash::String->[String]completeBashprog=["# 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]----------------------------------------------------------------------- ZSH SCRIPTcompleteZsh::String->[String]completeZsh_=["echo TODO: help add Zsh completions to cmdargs programs"]