Terminal-like Application Design

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
6 messages Options
Reply | Threaded
Open this post in threaded view
|

Terminal-like Application Design

Jeff Wheeler-2
Hi,

I'm a slight Haskell newbie, but I'm trying to write a terminal-like  
application that accepts simple commands with optional arguments, and  
can then execute them. Most of these commands will need IO, as later I  
will want to communicate over USB for most of them.

I was hoping, though, that I could get some comments on the initial  
architecture I've been playing with [1].

I suspect I should be using some sort of monad to represent the  
commands, but I don't fully understand monads, and am not sure how it  
would apply in this context.

Should I be using a monad here, and if so, how?

Thanks in advance,
Jeff Wheeler

[1] http://media.nokrev.com/junk/cli/
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: Terminal-like Application Design

Arnar Birgisson
Hi Jeff,

On Fri, Oct 17, 2008 at 01:29, Jeff Wheeler <[hidden email]> wrote:
> I'm a slight Haskell newbie, but I'm trying to write a terminal-like
> application that accepts simple commands with optional arguments, and can
> then execute them. Most of these commands will need IO, as later I will want
> to communicate over USB for most of them.

Do you mean a terminal application that runs a "session", accepting
commands interactively? If so, check out Shellac [1].

[1] http://www.cs.princeton.edu/~rdockins/shellac/home/

cheers,
Arnar
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: Terminal-like Application Design

Jason Dusek
In reply to this post by Jeff Wheeler-2
Jeff Wheeler <[hidden email]> wrote:
> I suspect I should be using some sort of monad to represent the commands,
> but I don't fully understand monads, and am not sure how it would apply in
> this context.
>
> Should I be using a monad here, and if so, how?

  At risk of stating the obvious, you will need to us the IO
  monad...

  When you say "use a monad to represent" the commands, that is
  perhaps not necessary. You can represent the commands as data,
  for example:

    data MyLanguage = Go | Stop | Left | Right

    runMyLanguage :: MyLanguage -> IO ()

  The `runMyLanguage` function serves to transform commands
  as pure data into real IO actions.

--
_jsn
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: Terminal-like Application Design

Allan Clark
In reply to this post by Jeff Wheeler-2
Hi Jeff

It sounds like maybe you just want an application that works a bit like 'cabal'.
So with cabal the first argument is taken as the 'command' and then the rest are based on that:

cabal build --some other --options --which may --or --may --not have --arguments

Yi has a simple template for a script which should get you started, please find it attached.

So here instead of processOptions, you might want, processCommand

processCommand :: [ String ] -> IO ()
processCommand ("build" : args) = processBuildCommand args
processCommand ("play" : args)  = processPlayCommand args
processCommand []               = putStrLn "You must supply a command"
processCommand _                = putStrLn "Sorry I don't understand your command" --Probably out put help here as well

processBuildCommand :: [ String ] -> IO ()
processBuildCommand = similar to the processOptions except now you are sure you are in a 'build' command

you *might* even have a separate set of option descreptions for each command.

hth
allan




Jeff Wheeler wrote:

> Hi,
>
> I'm a slight Haskell newbie, but I'm trying to write a terminal-like
> application that accepts simple commands with optional arguments, and
> can then execute them. Most of these commands will need IO, as later I
> will want to communicate over USB for most of them.
>
> I was hoping, though, that I could get some comments on the initial
> architecture I've been playing with [1].
>
> I suspect I should be using some sort of monad to represent the
> commands, but I don't fully understand monads, and am not sure how it
> would apply in this context.
>
> Should I be using a monad here, and if so, how?
>
> Thanks in advance,
> Jeff Wheeler
>
> [1] http://media.nokrev.com/junk/cli/
> _______________________________________________
> Haskell-Cafe mailing list
> [hidden email]
> http://www.haskell.org/mailman/listinfo/haskell-cafe
>

--
The University of Edinburgh is a charitable body, registered in
Scotland, with registration number SC005336.


{-
-}
module Main
  ( main )
where

{- Standard Library Modules Imported -}
import System.Console.GetOpt
  ( getOpt
  , usageInfo
  , ArgOrder    ( .. )
  , OptDescr    ( .. )
  , ArgDescr    ( .. )
  )
import System.Environment
  ( getArgs
  , getProgName
  )
{- External Library Modules Imported -}
{- Local Modules Imported -}
{- End of Imports -}

data CliFlag =
    CliHelp
  | CliVersion
  deriving Eq


options :: [ OptDescr CliFlag ]
options =
  [ Option   "h"     [ "help" ]
    (NoArg CliHelp)
    "Print the help message to standard out and then exit"

  , Option   "v"     [ "version" ]
    (NoArg CliVersion)
    "Print out the version of this program"
  ]

helpMessage :: String -> String
helpMessage progName =
  usageInfo progName options

versionMessage :: String -> String
versionMessage progName =
  progName ++ ": This is version 0.001"

-- | The main exported function
main :: IO ()
main = getArgs >>= processOptions

processOptions :: [ String ] -> IO ()
processOptions cliArgs =
  case getOpt Permute  options cliArgs of
    (flags, args, [])       ->
      processArgs flags args
    (_flags, _args, errors) ->
      do progName <- getProgName
         ioError $ userError (concat errors ++ helpMessage progName)

-- We assume all of the arguments are files to process
processArgs :: [ CliFlag ] -> [ String ] -> IO ()
processArgs flags files
  | elem CliHelp flags    = getProgName >>= (putStrLn . helpMessage)
  | elem CliVersion flags = getProgName >>= (putStrLn . versionMessage)
  | otherwise             = mapM_ processFile files

-- Our processing of a file is to simply count the words
-- in the file and output the number as a line.
processFile :: FilePath -> IO ()
processFile file =
  do contents <- readFile file
     putStrLn (show $ length $ words contents)


_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: Terminal-like Application Design

Magnus Therning
2008/10/17 allan <[hidden email]>:

> Hi Jeff
>
> It sounds like maybe you just want an application that works a bit like 'cabal'.
> So with cabal the first argument is taken as the 'command' and then the rest are based on that:
>
> cabal build --some other --options --which may --or --may --not have --arguments
>
> Yi has a simple template for a script which should get you started, please find it attached.
>
> So here instead of processOptions, you might want, processCommand
>
> processCommand :: [ String ] -> IO ()
> processCommand ("build" : args) = processBuildCommand args
> processCommand ("play" : args)  = processPlayCommand args
> processCommand []               = putStrLn "You must supply a command"
> processCommand _                = putStrLn "Sorry I don't understand your command" --Probably out put help here as well
>
> processBuildCommand :: [ String ] -> IO ()
> processBuildCommand = similar to the processOptions except now you are sure you are in a 'build' command
>
> you *might* even have a separate set of option descreptions for each command.
I wanted to throw in another idea, something I didn't come up with
myself but used in omnicodec[1].  Now I don't remember where I picked
up the idea:

1. Keep all option values as members of a type T
2. Define an instance of T with default values, dT
3. When using getOpt let the type of the options be something like
[OptDescr (T -> IO T)]
4. To get the final set of values fold (>>=) over all the list of
arguments returned from getOpt and using dT as the start value.
Something like 'effectiveT <- foldl (>>=) dT arguments'

In a tool I recently started working on I decided not to work in IO
and instead I ended up with something like 'effectiveT = (foldl (.) id
arguments) dT'.

/M

[1]: http://hackage.haskell.org/cgi-bin/hackage-scripts/package/omnicodec

--
Magnus Therning                        (OpenPGP: 0xAB4DFBA4)
magnus@therning.org          Jabber: magnus@therning.org
http://therning.org/magnus         identi.ca|twitter: magthe

_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe
Reply | Threaded
Open this post in threaded view
|

Re: Terminal-like Application Design

Dougal Stanton-2
2008/10/17 Magnus Therning <[hidden email]>:
>
> I wanted to throw in another idea, something I didn't come up with
> myself but used in omnicodec[1].  Now I don't remember where I picked
> up the idea:

This method is described in
<http://www.haskell.org/haskellwiki/High-level_option_handling_with_GetOpt>.

I have used it in the past and eventually end up with huge Config data
structures and not much separation of unrelated parts of the
computation. Of course, this is probably just me ;-)

Alternatively (and this is where I have had more success) you can take
several passes over the command arguments, using different parsers.
Each parser just ignores what it doesn't recognise:

parseArgs names = do
    args <- getArgs
    let info = parseWith infoOptions []
    let options = parseWith argOptions defaultOptArgs
    let query = parseWith queryOptions defaultQuery
    return (info, options, query)
 where parseWith os z = case getOpt Permute os args of
                            (o,_,_) -> foldr id z o

Note that this method tends to ignore user error at the command line,
but I'm sure a hybrid could be constructed that was more chatty but
still quite clean at the back end.

See <http://www.dougalstanton.net/code/buses/> for this code in its
wider context.

Cheers,


D

--
Dougal Stanton
[hidden email] // http://www.dougalstanton.net
_______________________________________________
Haskell-Cafe mailing list
[hidden email]
http://www.haskell.org/mailman/listinfo/haskell-cafe