I would like to use cmdargs to pass arguments to my Haskell program. For some reasons, I would like some options to be hidden (not shown but usable) in the cmdargs help message.
Is there a way to do to that?
I'm using cmdargs 0.10.9.
The idea here is to intercept the --help processing and edit the
help text to remove any references to hidden options before displaying it.
Options we wish to hide can be marked with special help text (e.g. &= help "HIDDEN OPTION")
and those lines can be removed from the help output.
Consider this simple main program:
main = cmdArgs sampleArgs >>= print
The path taken when --help is present is as follows:
cmdArgs = cmdArgsRun . cmdArgsMode
cmdArgs m = cmdArgsApply =<< processArgs m
cmdArgsApply :: CmdArgs a -> IO a
cmdArgsApply CmdArgs{..}
| Just x <- cmdArgsHelp = do putStr x; exitSuccess
| Just x <- cmdArgsVersion = ...
| otherwise = ...
To intercept help processing we can write our main like this:
main = do
b <- processArgs (cmdArgsMode sampleArgs)
a <- myApply b
print a
where myApply is a version of cmdArgsApply which emits the edited
version of the help text.
Here is working example. The options -s and --secret will not be shown in the help text. Try it with these arguments:
prog
prog --help
prog --version
prog -s1234
Program:
{-# LANGUAGE DeriveDataTypeable, RecordWildCards #-}
import Data.List (isInfixOf)
import System.Console.CmdArgs
import System.Console.CmdArgs.Explicit (processArgs)
import System.Exit (exitSuccess)
data MyArgs = MyArgs { file :: String, dest :: String, secret :: String }
deriving (Show, Data)
sample = MyArgs { file = "input.txt" &= help "the input file" &= opt "input"
, dest = "output.txt" &= help "the output file"
, secret = "3l33t" &= help "HIDDEN OPTION" &= opt "someflag"
}
main = do
b <- processArgs (cmdArgsMode sample)
a <- myApply b
putStrLn $ "running program with " ++ show a
myApply :: CmdArgs a -> IO a
myApply b#CmdArgs{..}
| Just x <- cmdArgsHelp = do putStr (fixupHelp x); exitSuccess
| otherwise = cmdArgsApply b
fixupHelp = unlines
. filter (not . ("HIDDEN OPTION" `isInfixOf`))
. lines
Related
I would like to add synonyms for the subcommands in my Haskell command line tool. For example summarise and summarize should yield the same result. Of course I could just add an entirely separate command summarize, that appears as an own element in the output of --help. But maybe there is a more elegant way.
Here is a self-contained example in a stack script opt_ex.hs:
#!/usr/bin/env stack
-- stack --resolver lts-18.17 script --package optparse-applicative
import Options.Applicative
import Data.Semigroup ((<>))
data Options = CmdGreet GreetArgs | CmdGroot GreetArgs
newtype GreetArgs = GreetArgs String
main :: IO ()
main = do
cmdOpts <- customExecParser (prefs showHelpOnEmpty) (info optParser fullDesc)
runCmd cmdOpts
optParser :: Parser Options
optParser = subparser (
command "greet" (info (CmdGreet <$> sample) (progDesc "Print greeting 1")) <>
command "groot" (info (CmdGroot <$> sample) (progDesc "Print greeting 2"))
)
runCmd :: Options -> IO ()
runCmd o = case o of
CmdGreet opts -> greet opts
CmdGroot opts -> groot opts
greet :: GreetArgs -> IO ()
greet (GreetArgs h) = putStrLn $ "Hello, " ++ h ++ "!"
groot :: GreetArgs -> IO ()
groot (GreetArgs h) = putStrLn $ "Howdy, " ++ h ++ "!"
sample :: Parser GreetArgs
sample = GreetArgs <$> strArgument ( metavar "TARGET" )
You can run this with ./opt_ex.hs greet John to get Hello, John! and with ./opt_ex.hs groot John to get Howdy, John!. Running ./opt_ex.hs will give you the following overview:
Usage: opt_ex.hs COMMAND
Available commands:
greet Print greeting 1
groot Print greeting 2
What would be the most elegant way, to add a command gruut in this example, which behaves exactly like greet, but produces the least amount of overhead, both in the code and for the user?
Ideally I would like ./opt_ex.hs to yield something like this:
Usage: opt_ex.hs COMMAND
Available commands:
greet|gruut Print greeting 1
groot Print greeting 2
I don't think you can do this. It works fine for options, because the definition of OptField contains a list of OptName, and adds to that list when you use (<>). But the definition of CommandFields, the thing returned by command, is
data CommandFields a = CommandFields
{ cmdCommands :: [(String, ParserInfo a)]
, cmdGroup :: Maybe String }
Each String name is thus associated with a different ParserInfo. Of course, you can define a variable containing any ParserInfo you like, and reuse it across two commands, so you won't have to repeat the ParserInfo. But as far as optparse-applicative is concerned, those two commands are distinct, so it will list them separately in the help text. For your example, this would look like
optParser = let greeting1 = info (CmdGreet <$> sample) (progDesc "Print greeting 1")
in subparser $
command "greet" greeting1 <>
command "gruut" greeting1 <>
command "groot" (info (CmdGroot <$> sample) (progDesc "Print greeting 2"))
and indeed, when run we see both commands listed:
Usage: optparse COMMAND
Available commands:
greet Print greeting 1
gruut Print greeting 1
groot Print greeting 2
So I'm going through the examples in the CmdArgs documentation, and I'm trying to build off the Hello World program in order to make a simple CLI program that takes a filename as input, and just shows the contents of that file (like cat). But I'm a Haskell beginner, and have virtually no idea what I'm doing. Here's what I have so far:
{-# LANGUAGE DeriveDataTypeable #-}
module ShowFile where
import System.Console.CmdArgs
data ShowFile = ShowFile {file :: Maybe FilePath}
deriving (Show, Data, Typeable)
showFile = ShowFile
{file = def &= typ "FILE" &= argPos 0}
main = print =<< cmdArgs showFile
And so running runhaskell mycat.hs test.txt shows:
ShowFile {file = Just "test.txt"}
So something's working! Now how do I get it to display the contents of test.txt instead? I've been trying things like:
main = getContents showFile
but haven't stumbled on the right thing yet.
Pattern match on the option.
main = do
options <- cmdArgs showFile
contents <- case options of
ShowFile { file = Nothing } -> getContents
ShowFile { file = Just f } -> readFile f
putStr contents
Hi so I have this main method which runs a parser,
main = do
args <- getArgs
let filename = head args
contents <- readFile filename
let c = parse parseProgram contents
putStrLn "------------------------------------------------"
putStrLn "THE PROGRAM WE HAVE PARSED"
putStrLn "------------------------------------------------"
putStrLn (show ((fst.head) c))
return ()
when I run this program the first three calls to putStrLn are not printed to the terminal, it only shows the parsed program.
any help will be appreciated, how do I get all the calls to print?
This doesn't appear possible. I created a more minimal example following as closely as possible to your code. I used Parsec because I am not sure what parsing library you are using.
Contents of parsec-trivial.hs:
#!/usr/bin/env stack
{- stack
--resolver lts-6.15
--install-ghc
runghc
--package parsec
-}
import System.Environment
import Text.ParserCombinators.Parsec
import Text.ParserCombinators.Parsec.Char
ws = many space
nat :: Parser Integer
nat = read <$> many digit
parseProgram = ws *> nat <* ws <* eof
main = do
args <- getArgs
let filename = head args
contents <- readFile filename
let c = parse parseProgram filename contents
putStrLn "------------------------------------------------"
putStrLn "THE PROGRAM WE HAVE PARSED"
putStrLn "------------------------------------------------"
putStrLn $ show c
Contents of foo.b:
42
Executing this program goes like this:
$ ./parsec-trivial.hs foo.b
------------------------------------------------
THE PROGRAM WE HAVE PARSED
------------------------------------------------
Right 42
I'm very new to Haskell and I'm trying to parse a map file, just for practice. My code will compile, but it gives me the wrong result. All I get is "Right []" - which I don't understand.
My code is very similar to the tutorial here, but I rewrote it to serve my needs.
My file looks like this (I removed most of the lines to save space here):
#test map 2
0,0:1;
1,0:1;
2,0:1;
3,0:1;
My code:
import Data.Word
import Data.Time
import Data.Attoparsec.Char8
import Control.Applicative
import qualified Data.ByteString as B
-- Types --
data Tile = Tile Int Int Int deriving Show
data MapLine =
MapLine { tile :: Tile } deriving Show
-- Parsing --
parseTile :: Parser Tile
parseTile = do
x <- decimal
char ','
y <- decimal
char ':'
t <- decimal
char ';'
return $ Tile x y t
mapLineParser :: Parser MapLine
mapLineParser = do
t <- parseTile
return $ MapLine t
fileParser :: Parser [MapLine]
fileParser = many $ mapLineParser <* endOfLine
-- Main --
main :: IO()
--main = B.readFile "map.hexmap" >>= print . parseOnly fileParser
main = do
print "Parsing map..."
let x = B.readFile "map.hexmap"
x >>= print . parseOnly fileParser
print "Done."
Thanks for the help.
Your parser "successfully parses" a list of MapLines of length zero before failing at the first line. Remove that line (and make sure your file doesn't include any non-parsable bytes at the start like a BOM) and it should work. Or write a parser for lines starting with a # that ignores the result, then combine.
Hey, sorry to dump the error message here but I've tried everything I can find and nothing seems relevant. This code is generating the error:
import System.Environment
import System.Directory
import System.IO
import Data.List
data Node = PathNode String Float Float [String] | NoNode deriving (Show)
main = do
(filename:args) <- getArgs
load filename
load :: String -> IO ()
load fileName = do
contents <- readFile fileName
let pathStrings = lines contents
first = head pathStrings
args = lines first
path = createNode args
putStr path
createNode [String] -> Node
createNode (name:x:y:paths) = PathNode name x y paths
createNode [] = NoNode
I know its something to do with alignment, but I have aligned all the calls in the 'load' function correctly. What am I doing wrong?
Thanks
-A
The last line in the do expression is indented too far.
Also, you can just write
load :: String -> IO ()
load fileName =
putStr . createNode . lines . head . lines =<< readFile filename
Only errors I can find besides identation are:
in load: putStr expects String, whereas path is of type Node.
in createNode: Pathnode needs x and y to be of type Float, whereas you give Strings.
load :: String -> IO ()
load fileName = do
contents <- readFile fileName
let pathStrings = lines contents
first = head pathStrings
args = lines first
path = createNode args
putStr $ show path
createNode :: [String] -> Node
createNode (name:x:y:paths) = PathNode name (read x) (read y) paths
createNode [] = NoNode
This solves both type errors described by using show and read.