Shellac-0.9.1: src/System/Console/Shell/Commands.hs
{-
-
- Copyright 2005-2007, Robert Dockins.
-
-}
module System.Console.Shell.Commands
( File (..)
, Username (..)
, Completable (..)
, Completion (..)
, showShellHelp
, showCmdHelp
, helpCommand
, exitCommand
, toggle
, cmd
, CommandFunction
, maybePrefix
, getShellCommands
, commandsRegex
) where
import System.Console.Shell.Types
import System.Console.Shell.PPrint
import System.Console.Shell.Regex
import System.Console.Shell.ShellMonad
maybePrefix :: ShellDescription st -> String
maybePrefix desc = case commandStyle desc of CharPrefixCommands x -> [x]; _ -> ""
getShellCommands :: ShellDescription st -> [(String,CommandParser st,Doc,Doc)]
getShellCommands desc = map ($ desc) (shellCommands desc)
-- | Represents a command argument which is a filename
newtype File = File String
-- | Represents a command argument which is a username
newtype Username = Username String
-- | Represents a command argument which is an arbitrary
-- completable item. The type argument determines the
-- instance of 'Completion' which is used to create
-- completions for this command argument.
newtype Completable compl = Completable String
------------------------------------------------------------------
-- | A typeclass representing user definable completion functions.
class Completion compl st | compl -> st where
-- | Actually generates the list of possible completions, given the
-- current shell state and a string representing the beginning of the word.
complete :: compl -> (st -> String -> IO [String])
-- | generates a label for the argument for use in the help displays.
completableLabel :: compl -> String
-----------------------------------------------------------------------
-- | Prints the help message for this shell, which lists all avaliable
-- commands with their syntax and a short informative message about each.
showShellHelp :: ShellDescription st -> String
showShellHelp desc = show (commandHelpDoc desc (getShellCommands desc)) ++ "\n"
-------------------------------------------------------------------------
-- | Print the help message for a particular shell command
showCmdHelp :: ShellDescription st -> String -> String
showCmdHelp desc cmd =
case cmds of
[_] -> show (commandHelpDoc desc cmds) ++ "\n"
_ -> show (text "bad command name: " <> squotes (text cmd)) ++ "\n"
where cmds = filter (\ (n,_,_,_) -> n == cmd) (getShellCommands desc)
commandHelpDoc :: ShellDescription st -> [(String,CommandParser st,Doc,Doc)] -> Doc
commandHelpDoc desc cmds =
vcat [ (fillBreak 20 syn) <+> msg | (_,_,syn,msg) <- cmds ]
------------------------------------------------------------------------------
-- | Creates a shell command which will exit the shell.
exitCommand :: String -- ^ the name of the command
-> ShellCommand st
exitCommand name desc = ( name
, \_ -> [CompleteParse (shellSpecial ShellExit)]
, text (maybePrefix desc) <> text name
, text "Exit the shell"
)
--------------------------------------------------------------------------
-- | Creates a command which will print the shell help message.
helpCommand :: String -- ^ the name of the command
-> ShellCommand st
helpCommand name desc = ( name
, \_ -> [CompleteParse (shellSpecial (ShellHelp Nothing))]
, text (maybePrefix desc) <> text name
, text "Display the shell command help"
)
---------------------------------------------------------
-- | Creates a command to toggle a boolean value
toggle :: String -- ^ command name
-> String -- ^ help message
-> (st -> Bool) -- ^ getter
-> (Bool -> st -> st) -- ^ setter
-> ShellCommand st
toggle name helpMsg getter setter desc =
( name
, \_ -> [CompleteParse doToggle]
, text (maybePrefix desc) <> text name
, text helpMsg
)
where doToggle = do
st <- getShellSt
if getter st
then shellPutInfoLn (name++" off") >> putShellSt (setter False st)
else shellPutInfoLn (name++" on") >> putShellSt (setter True st)
-------------------------------------------------------------------
-- | Creates a user defined shell commmand. This relies on the
-- typeclass machenery defined by 'CommandFunction'.
cmd :: CommandFunction f st
=> String -- ^ the name of the command
-> f -- ^ the command function. See 'CommandFunction' for restrictions
-- on the type of this function.
-> String -- ^ the help string for this command
-> ShellCommand st
cmd name f helpMsg desc =
( name
, parseCommand (wordBreakChars desc) f
, text (maybePrefix desc) <> text name <+> hsep (commandSyntax f)
, text helpMsg
)
------------------------------------------------------------------------------
-- | This class is used in the 'cmd' function to automaticly generate
-- the command parsers and command syntax strings for user defined
-- commands. The type of \'f\' is restricted to have a restricted set of
-- monomorphic arguments ('Int', 'Integer', 'Float', 'Double', 'String',
-- 'File', 'Username', and 'Completable') and the head type must be @Sh st ()@
--
-- > f :: Int -> File -> Sh MyShellState ()
-- > g :: Double -> Sh st ()
-- > h :: Sh SomeShellState ()
--
-- are all legal types, whereas:
--
-- > bad1 :: a -> Sh (MyShellState a) ()
-- > bad2 :: [Int] -> Sh MyShellState ()
-- > bad3 :: Bool -> MyShellState
--
-- are not.
class CommandFunction f st | f -> st where
parseCommand :: String -> f -> CommandParser st
commandSyntax :: f -> [Doc]
-------------------------------------------------------------
-- Instances for the base case
instance CommandFunction (Sh st ()) st where
parseCommand wbc m str =
do (x,[]) <- runRegex (maybeSpaceBefore (Epsilon (CompleteParse m))) str
return x
commandSyntax _ = []
--------------------------------------------------------------
-- Instances for the supported command argument types
instance CommandFunction r st
=> CommandFunction (Int -> r) st where
parseCommand = doParseCommand Nothing intRegex id
commandSyntax f = text (show intRegex) : commandSyntax (f undefined)
instance CommandFunction r st
=> CommandFunction (Integer -> r) st where
parseCommand = doParseCommand Nothing intRegex id
commandSyntax f = text (show intRegex) : commandSyntax (f undefined)
instance CommandFunction r st
=> CommandFunction (Float -> r) st where
parseCommand = doParseCommand Nothing floatRegex id
commandSyntax f = text (show floatRegex) : commandSyntax (f undefined)
instance CommandFunction r st
=> CommandFunction (Double -> r) st where
parseCommand = doParseCommand Nothing floatRegex id
commandSyntax f = text (show floatRegex) : commandSyntax (f undefined)
instance CommandFunction r st
=> CommandFunction (String -> r) st where
parseCommand wbc = doParseCommand Nothing (wordRegex wbc) id wbc
commandSyntax f = text (show (wordRegex "")) : commandSyntax (f undefined)
instance CommandFunction r st
=> CommandFunction (File -> r) st where
parseCommand wbc = doParseCommand
(Just FilenameCompleter)
(wordRegex wbc)
File
wbc
commandSyntax f = text "<file>" : commandSyntax (f undefined)
instance CommandFunction r st
=> CommandFunction (Username -> r) st where
parseCommand wbc = doParseCommand
(Just UsernameCompleter)
(wordRegex wbc)
Username
wbc
commandSyntax f = text "<username>" : commandSyntax (f undefined)
instance (CommandFunction r st,Completion compl st)
=> CommandFunction (Completable compl -> r) st where
parseCommand wbc =
( doParseCommand
(Just (OtherCompleter (complete (undefined::compl))))
(wordRegex wbc)
Completable
wbc
) :: (Completable compl -> r) -> CommandParser st
commandSyntax (f:: (Completable compl -> r)) =
text (completableLabel (undefined::compl)) : commandSyntax (f undefined)
----------------------------------------------------------------
-- Helper functions used in the above instance declarations
-- These make use of the hackish regex library.
doParseCommand compl re proj wbc f [] = return (IncompleteParse compl)
doParseCommand compl re proj wbc f str =
let xs = runRegex (maybeSpaceBefore (maybeSpaceAfter re)) str
in case xs of
[] -> return (IncompleteParse compl)
_ -> do (x,str') <- xs; parseCommand wbc (f (proj x)) str'
commandsRegex :: ShellDescription st -> Regex Char (String,CommandParser st,Doc,Doc)
commandsRegex desc =
case commandStyle desc of
CharPrefixCommands ch -> prefixCommandsRegex ch (getShellCommands desc)
OnlyCommands -> onlyCommandsRegex (getShellCommands desc)
SingleCharCommands -> singleCharCommandRegex (getShellCommands desc)
onlyCommandsRegex :: [(String,CommandParser st,Doc,Doc)] -> Regex Char (String,CommandParser st,Doc,Doc)
onlyCommandsRegex xs =
Concat (\_ x -> x) maybeSpaceRegex $
Concat (\x _ -> x) (anyOfRegex (map (\ (x,y,z,w) -> (x,(x,y,z,w))) xs)) $
spaceRegex
prefixCommandsRegex :: Char -> [(String,CommandParser st,Doc,Doc)] -> Regex Char (String,CommandParser st,Doc,Doc)
prefixCommandsRegex ch xs =
Concat (\_ x -> x) maybeSpaceRegex $
Concat (\_ x -> x) (strTerminal ch) $
Concat (\x _ -> x) (anyOfRegex (map (\ (x,y,z,w) -> (x,(x,y,z,w))) xs)) $
spaceRegex
singleCharCommandRegex :: [(String,CommandParser st,Doc,Doc)] -> Regex Char (String,CommandParser st,Doc,Doc)
singleCharCommandRegex xs =
altProj
(anyOfRegex (map (\ (x,y,z,w) -> ([head x],(x,y,z,w))) xs))
(Epsilon ("",\_ -> [CompleteParse (shellSpecial ShellNothing)],empty,empty))