How to instruct cmdargs to not parse args beyond some point? - haskell

I am working on a program that takes another command with its arguments:
$ myprog record -f 1.txt ls
$ myprog record -f 1.txt ls -l
Something like sudo:
$ sudo ls
$ sudo ls -l
Here is my code that uses cmdargs:
#!/usr/bin/env stack
-- stack --resolver lts-9.20 script --package cmdargs
{-# LANGUAGE DeriveDataTypeable #-}
module Main where
import System.Console.CmdArgs as Args
data Tape = Record {file :: Maybe String, command :: String, commandArgs :: [String]}
| Replay
deriving (Data, Typeable, Show, Eq)
main :: IO ()
main = do
m <- cmdArgs $ modes
[ Record { file = def &= typ "FILE"
, command = def &= typ "COMMAND" &= argPos 0
, commandArgs = def &= typ "ARGS" &= args }
, Replay ]
print m
It works nicely, but only for commands that don't have their own flags:
$ ./cmdargs.hs record -f 1.txt ls
Record {file = Just "1.txt", command = "ls", commandArgs = []}
$ ./cmdargs.hs record -f 1.txt ls 1 2 3
Record {file = Just "1.txt", command = "ls", commandArgs = ["1","2","3"]}
$ ./cmdargs.hs record -f 1.txt ls -l
Unknown flag: -l
Q: How to instruct cmdargs to keep "commandArgs" as is, without looking for flags in it?

The traditional method is not within the parser but using a -- argument, indicating the end of flags:
$ ./args record -- something -l
Record {file = Nothing, command = "something", commandArgs = ["-l"]}
Unfortunately I don't know how to tell getArgs to do so at an arbitrary place, but I imagine it would have something to do with modes.

Related

How do I specify names for missing commands in the help message generated by optparse-applicative?

I am attempting to use Hackage's optparse-applicative package and have a question regarding how to specify a certain aspect of the help message displayed when the program is run with insufficient commands specified.
The following example program illustrates my issue. When run from the command line it takes one of two commands as input. That is, it is intended to be run either as $ program com1 or $ program com2.
module Main where
import Options.Applicative
import Data.Semigroup ((<>))
data Command = Com1
| Com2
com1 :: Parser Command
com1 = subparser $ command "com1" $ info (pure Com1) fullDesc
com2 :: Parser Command
com2 = subparser $ command "com2" $ info (pure Com2) fullDesc
commandParser :: Parser Command
commandParser = com1
<|> com2
runCommand :: Command -> IO ()
runCommand Com1 = putStrLn ">>> Com1 <<<"
runCommand Com2 = putStrLn ">>> Com2 <<<"
opts :: ParserInfo Command
opts = info (commandParser <**> helper)
$ fullDesc
<> progDesc "=== progDesc ==="
<> header "=== header ==="
<> footer "=== footer ==="
main :: IO ()
main = runCommand =<< execParser opts
When this program is run with neither command com1 nor com2 specified, a help message is displayed.
$ program
Missing: (COMMAND | COMMAND)
Usage: options-applicative-example-exe (COMMAND | COMMAND)
=== progDesc ===
This help message displays (COMMAND | COMMAND) instead of (com1 | com2), and I think that specifying the names in this help message would be clearer and more useful.
Specifying the --help option as in $ program --help gives a different output.
$ program --help
=== header ===
Usage: options-applicative-example-exe (COMMAND | COMMAND)
=== progDesc ===
Available options:
-h,--help Show this help text
Available commands:
com1
com2
=== footer ===
The command names com1 and com2 are listed in the "Available commands" section. Here too, however, I think the usage section would be clearer as (com1 | com2) instead of (COMMAND | COMMAND).
How can I specify the usage section of the help message to be (com1 | com2) instead of (COMMAND | COMMAND)?
It seems you can use metavar on the command.
com1 = subparser $ mconcat
[ metavar c
, command c $ info (pure Com1) fullDesc)
] where c = "com1"
Whereas here each command is its own subparser, the docs of optparse-applicative prefer to combine command modifiers first before applying subparser to the whole, so we would only see a single COMMAND and metavar wouldn't work well.

How to use readProcessWithExitCode?

This command runs fine in my terminal:
grep --include=\\*.txt --recursive --regexp='answer'
This one runs fine in ghci:
import System.Process
r <- readCreateProcessWithExitCode (shell "grep --include=\\*.txt --recursive --regexp='answer'") ""
But this one fails in ghci:
import System.Process
r <- readProcessWithExitCode "grep" ["--include=\\*.txt", "--recursive", "--regexp='answer'"] ""
It returns (ExitFailure 1,"","").
Am I doing something bad?
Update
This one works:
readProcessWithExitCode "grep" ["-r", "-e 'answer'"]
It looks like one cannot set options starting with --.
Are you sure that this command work in your terminal:
grep --include=\\*.txt --recursive --regexp='answer'
On my system I get the warning:
grep: warning: recursive search of stdin
Try adding . as an additional argument:
readProcessWithExitCode "grep" ["--include=\\*.txt", "--recursive", "--regexp='answer'", "."] ""
Update
This simpler way of passing arguments to grep works for me:
readProcessWithExitCode "grep" ["--include", "*.hs", "--recursive", "--regexp", "answer", "."] ""

How to send text to GHCi process?

I'm working on Haskell presentation engine Howerpoint. It is running in GHCi. I would like to create a function which would output a statement to current running GHCi session. It must work on Linux and Mac, Windows is not necessary. Function probably will have type
executeStatement :: String -> IO ()
What I tried already:
getProcessID and getParentProcessID and then sending something like
echo 'xxx' > /proc/92856/fd/1
-bash: /proc/92856/fd/1: No such file or directory
I also tried runCommand but it executes command in the Bash and not in GHCi so I got error that the command was not found
xdotool does not run on mac
You could use the ghcid project from hackage to evaluate expressions. They would not be evaluated in the same session as your are currently running, but you can send expressions and read their output in a session nonetheless.
Here is an example:
import Language.Haskell.Ghcid
main :: IO ()
main = do
(g, _) <- startGhci "ghci" (Just ".") True
let executeStatement = exec g
executeStatement "let x = 33"
executeStatement "x + 8" >>= print . head
executeStatement "print x" >>= print . head
stopGhci g
The output is "41" "33" and g represents a ghci session.
If you really need to execute expressions in an already running ghci instance you can have a look at this function - startGhci and instead of creating a new process you would have to tap into the existing process and then set std_in, std_out and std_err.
You could use a terminal multiplexer like tmux to execute ghci in one pane, and from another pane invoke tmux commands that send keystrokes to ghci.
tmux load-buffer lets you load text into a tmux clipboard (using - as the path reads from stdin).
# from within tmux
$ echo foo | tmux load-buffer -
$ tmux show-buffer
foo
tmux paste-buffer lets you paste the contents of a tmux clipboard into a pane:
$ tmux list-panes
0: [127x24] [history 1850/2000, 1343570 bytes] %0
1: [127x24] [history 0/2000, 0 bytes] %2 (active)
$ tmux paste-buffer -t %0
Another option, already mentioned in the comments, is to use the process library to launch a ghci process, and send the commands through piped stardard input.
Here's a small program that uses my process-streaming helper package for process (not really required, you could do the same using process alone). stdin is piped, stdout and stderr inherited:
{-# LANGUAGE OverloadedStrings #-}
import System.Process.Streaming -- from process-streaming
import Pipes (lift,yield) -- from pipes
import Control.Concurrent (threadDelay)
main :: IO ()
main = executeInteractive program (feedProducer (do
let delay = lift (threadDelay (1000000*6))
delay
yield "4 + 3\n"
delay
yield "5 + 3\n"))
where
program = (shell "ghci"){ std_in = CreatePipe }
The output is:
$ ./Main
GHCi, version 7.10.2: http://www.haskell.org/ghc/ :? for help
Prelude> 7
Prelude> 8
Prelude> Leaving GHCi.

How to make R script takes input from pipe and user given parameter

I have the following R script (myscript.r)
#!/usr/bin/env Rscript
dat <- read.table(file('stdin'), sep=" ",header=FALSE)
# do something with dat
# later with user given "param_1"
With that script we can run it the following way;
$ cat data_no_*.txt | ./myscript.r
What I want to do is to make the script takes additional parameter from user:
$ cat data_no_*.txt | ./myscript.r param_1
What should I do to modify the myscript.r to accommodate that?
For very basic usage, have a look at ?commandArgs.
For more complex usage, two popular packages for command-line arguments and options parsing are getopt and optparse. I use them all the time, they get the job done. I also see argparse, argparser, and GetoptLong but have never used them before. One I missed: Dirk recommended that you look at docopt which does seem very nice and easy to use.
Finally, since you seem to be passing arguments via pipes you might find this OpenRead() function useful for generalizing your code and allowing your arguments to be pipes or files.
I wanted to test docopt so putting it all together, your script could look like this:
#!/usr/bin/env Rscript
## Command-line parsing ##
'usage: my_prog.R [-v -m <msg>] <param> <file_arg>
options:
-v verbose
-m <msg> Message' -> doc
library(docopt)
opts <- docopt(doc)
if (opts$v) print(str(opts))
if (!is.null(opts$message)) cat("MESSAGE: ", opts$m)
## File Read ##
OpenRead <- function(arg) {
if (arg %in% c("-", "/dev/stdin")) {
file("stdin", open = "r")
} else if (grepl("^/dev/fd/", arg)) {
fifo(arg, open = "r")
} else {
file(arg, open = "r")
}
}
dat.con <- OpenRead(opts$file_arg)
dat <- read.table(dat.con, sep = " ", header = FALSE)
# do something with dat and opts$param
And you can test running:
echo "1 2 3" | ./test.R -v -m HI param_1 -
or
./test.R -v -m HI param_1 some_file.txt
We built littler to support just that via its r executable.
Have a look at its examples, it may fit your bill.

Can I make readProcess drop the quotes?

I'm trying to write some code which runs grep externally, then analyses the output. Specifically, I want to do
grep <username> *.test
but, sadly,
readProcess "grep" [username, "*.test"]
seems to generate the command with double quotes around the arguments
grep "<username>" "*.test"
and as there is no individual file called asterisk-dot-test, grep barfs. There are files with .test extensions.
Can I persuade readProcess (or something similar) to issue the command I want?
"*" is expanded not by grep, but by shell. You should run something like sh -c 'grep username *.test if you want the expansion.
A better way is to use createProcess with ShellCommand argument.
You're probably best going to createProcess, which is the most general process creation function. Something like...
import System.Process
import System.IO
makeGrep username file = "grep " ++ username ++ " " ++ file
main :: IO ()
main = do
(_, Just hOut, _, hProc) <- createProcess (
(shell (makeGrep "bob" "*.test"))
{ std_out = CreatePipe }
)
exitCode <- waitForProcess hProc
output <- hGetContents hOut
print output
I usually use system from System.Cmd. You're supposed to build a correct Shell command (e.g. do all the escaping) but it has the advantage that it uses the String as it's provided.

Resources