I'm building a brainfuck compiler. The executable accepts two commands $ brainfuck compile ... and $ brainfuck run. I want the executable to auto complete when pressing tab. E.g. writing $ brainfuck com and then pressing tab should generate $ brainfuck compile.
data Command = Compile CompileArgs | Run RunArgs
deriving (Show)
main :: IO ()
main = execute =<< execParser opts
where
opts = info (helper <*> argsParser) fullDesc
execute :: Command -> IO ()
execute (Compile args) = compile args
execute (Run args) = run args
argsParser :: Parser Command
argsParser = subparser (compileCommand <> runCommand)
where
compileCommand = command "compile" $ info compileOptions $ progDesc "Compile brainfuck to an executable"
runCommand = command "run" $ info runOptions $ progDesc "Execute brainfuck code"
There is a section on optparse's github page here, but I don't really understand it.
The function completeWith :: Options.Applicative.Builder.Internal.HasCompleter f => [String] -> Mod f a looks quite similar to command :: String -> ParserInfo a -> Mod CommandFields a which I'm already using. So I figured I could use it and just combine them with <> but it turns out that CommandFields is not an instance of HasCompleter.
How are you supposed to get the auto completion to work?
After RTFM'ing a bit I found out how to configure the auto completion.
completeWith is applied when constructing the parsers for the individual arguments.
Like so:
data CompileArgs = CompileArgs
{
debug :: Bool,
optimizations :: OptimizationLevel,
file :: String
}
deriving (Show, Read)
compileArgsParser :: Parser CompileArgs
compileArgsParser = CompileArgs
<$> switch (
long "debug" <>
help "Outputs object and assembly files")
<*> option auto (
long "optimization-level" <>
value All <>
metavar "LEVEL" <>
help "all | none, default: all" <>
completeWith ["all", "none"])
<*> argument str (
metavar "FILE" <>
help "brainfuck source code" <>
action "file")
<**> helper
action is an instruction to bash on how auto complete. "file" means auto complete with any file or directory. See this page for more info.
In order for these auto completions to kick in you need to generate a script and make sure that script is sourced. By convention it's placed under /etc/bash_completion.d/ when using bash.
brainfuck --bash-completion-script `which brainfuck` | sudo tee /etc/bash_completion.d/brainfuck
in my case where my program is called brainfuck.
I have not tested this, but after reading the documentation, it seems to me that by calling execParser in main, your program automatically supports the required options for command complete. You just need to run your program with --bash-completion-script as documented to generate a shell script, and then load that script to bash.
Related
For some reason I cannot make cd command work in shake/command Haskell library. It thinks directory I called with cd does not exist even though it is present in the filesystem.
Here is an excerpt of my code:
dataDir = "data"
data Settings = Settings {
url :: String
} deriving (Eq, Show, Generic, JSON.ToJSON, JSON.FromJSON)
settings :: String -> Handler Settings
settings subfolder = let
gitPath = dataDir ++ "/" ++ subfolder ++ "/git/.git"
in do
pathExists <- liftIO $ doesPathExist gitPath
-- Stdout pwdOut <- liftIO $ cmd ("pwd" :: String)
-- liftIO $ putStrLn $ pwdOut
if not pathExists
then do
liftIO $ (cmd_ ("mkdir -p" :: String) [gitPath] :: IO ())
liftIO $ (cmd_ ("cd" :: String) [gitPath] :: IO ())
liftIO $ (cmd_ ("git init" :: String) :: IO ())
return $ Settings { url = ""}
else do
liftIO $ (cmd_ (Cwd ".") ("cd" :: String) [gitPath] :: IO ())
Stdout out <- liftIO $ (cmd ("git config --get remote.origin.url" :: String))
return $ Settings {url = out}
It fails with an error cd: createProcess: runInteractiveProcess: exec: does not exist (No such file or directory) in both cases: if dir exists and when mkdir command is executed.
Cannot wrap my head around it. But before I submit a bug to the shake's github page, I want to make sure with you I am not doing anything stupid that might cause this kind of behavior.
Thanks in advance for help.
As described in the other answer, cd is not an executable, so if you wanted to run it, you would have to pass Shell to cmd.
However, it is almost certainly the case that you don't want to call cd in a command, as it does not change the directory for any subsequent command. Each cmd is a separate process, with a separate environment, so the subsequent command will be in a fresh environment, and the same working directory as before the cd. The solution is to pass (Cwd gitPath) to each command you want to operate with the given directory.
Shake's Haddock page describes cmd_, and links to its source. There we can see that cmd_ eventually calls commandExplicitIO, which constructs a ProcessOpts with RawCommand and passes it to process. process then takes that ProcessOpts, pattern-matches it as a RawCommand (via cmdSpec), and calls proc. We have now entered the well-documented zone: you must give proc an executable, and cd is not an executable. (Why? Since processes cannot change the working directory of their parent, cd must be a shell builtin.)
In my Haskell executable, created using optparse-applicative, I would like to have a global option for --version alongside the global --help option that is available from all subcommands. However the example provided (see below) for adding a --version option to to a CLI with subcommands results in a --version option that is inconsistently available
$ cli create --version
Invalid option `--version'
Usage: cli create NAME
Create a thing
$ cli delete --version
0.0
and never shows up in help for subcommands
$ cli create -h
Usage: cli create NAME
Create a thing
Available options:
NAME Name of the thing to create
-h,--help Show this help text
$ cli delete -h
Usage: cli delete
Delete the thing
Available options:
-h,--help Show this help text
The behavior I would like is for --version to be available globally and to all subcommands:
$ cli create -h
Usage: cli create NAME
Create a thing
Available options:
NAME Name of the thing to create
--version Show version
-h,--help Show this help text
$ cli delete -h
Usage: cli delete
Delete the thing
Available options:
--version Show version
-h,--help Show this help text
$ cli create --version
0.0
$ cli delete --version
0.0
It's not clear from the documentation how to achieve this.
In fact, I'd ideally like to be able to clearly group options in the help output:
$ cli create -h
Usage: cli create NAME
Create a thing
Arguments:
NAME Name of the thing to create
Global options:
--version Show version
-h,--help Show this help text
$ cli delete -h
Usage: cli delete
Delete the thing
Global options:
--version Show version
-h,--help Show this help text
Is there a way to achieve this using optparse-applicative?
{-#LANGUAGE ScopedTypeVariables#-}
import Data.Semigroup ((<>))
import Options.Applicative
data Opts = Opts
{ optGlobalFlag :: !Bool
, optCommand :: !Command
}
data Command
= Create String
| Delete
main :: IO ()
main = do
(opts :: Opts) <- execParser optsParser
case optCommand opts of
Create name -> putStrLn ("Created the thing named " ++ name)
Delete -> putStrLn "Deleted the thing!"
putStrLn ("global flag: " ++ show (optGlobalFlag opts))
where
optsParser :: ParserInfo Opts
optsParser =
info
(helper <*> versionOption <*> programOptions)
(fullDesc <> progDesc "optparse subcommands example" <>
header
"optparse-sub-example - a small example program for optparse-applicative with subcommands")
versionOption :: Parser (a -> a)
versionOption = infoOption "0.0" (long "version" <> help "Show version")
programOptions :: Parser Opts
programOptions =
Opts <$> switch (long "global-flag" <> help "Set a global flag") <*>
hsubparser (createCommand <> deleteCommand)
createCommand :: Mod CommandFields Command
createCommand =
command
"create"
(info createOptions (progDesc "Create a thing"))
createOptions :: Parser Command
createOptions =
Create <$>
strArgument (metavar "NAME" <> help "Name of the thing to create")
deleteCommand :: Mod CommandFields Command
deleteCommand =
command
"delete"
(info (pure Delete) (progDesc "Delete the thing"))
As far as I know, this (in particular, the categorized help text) isn't really easy to do with optparse-applicative, since it isn't quite the pattern that they were planning for with global arguments. If you are okay with using program --global-options command --local-options (which is a fairly standard pattern) instead of program command --global-and-local-options, then you can use the approach shown in the linked example:
$ ./optparse-sub-example
optparse-sub-example - a small example program for optparse-applicative with
subcommands
Usage: optparse [--version] [--global-flag] COMMAND
optparse subcommands example
Available options:
-h,--help Show this help text
--version Show version
--global-flag Set a global flag
Available commands:
create Create a thing
delete Delete the thing
$ ./optparse-sub-example --version create
0.0
$ ./optparse-sub-example --version delete
0.0
$ ./optparse-sub-example --global-flag create HI
Created the thing named HI
global flag: True
$ ./optparse-sub-example --global-flag delete
Deleted the thing!
global flag: True
(Note: I would advise going with this approach, since "global options before the command" is fairly standard).
If you also want the global options to be available in every subcommand, you will have a few issues.
As far as I know, there is no way to affect the help text output in order to group them separately inside the individual command help texts.
You will need some custom subparser-like function that adds your global options & merges them with any global options before the command.
For #2, one way to restructure the example to support this might be something along these lines:
To start with, standard boilerplate and imports:
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE ApplicativeDo #-}
import Data.Monoid
import Data.Semigroup ((<>))
import Options.Applicative
import Options.Applicative.Types
Opts are explicitly split into optGlobals and optCommand, making it easy to deal with all of the global options at once if more are available:
data Opts = Opts
{ optGlobals :: !GlobalOpts
, optCommand :: !Command
}
data GlobalOpts = GlobalOpts { optGlobalFlag :: Bool }
GlobalOpts should be a Semigroup and a Monoid, since we need to merge options seen at various different points (before the command, after the command, etc.). It should also be possible, with suitable alterations to mysubparser below, to require global options to be given only after commands and omit this requirement.
instance Semigroup GlobalOpts where
-- Code for merging option parser results from the multiple parsers run
-- at various different places. Note that this may be run with the default
-- values returned by one parser (from a location with no options present)
-- and the true option values from another, so it may be important
-- to distinguish between "the default value" and "no option" (since "no
-- option" shouldn't override another value provided earlier, while
-- "user-supplied value that happens to match the default" probably should).
--
-- In this case this doesn't matter, since the flag being provided anywhere
-- should be enough for it to be considered true.
(GlobalOpts f1) <> (GlobalOpts f2) = GlobalOpts (f1 || f2)
instance Monoid GlobalOpts where
-- Default values for the various options. These should probably match the
-- defaults used in the option declarations.
mempty = GlobalOpts False
As before, a Command type to represent the different possible commands:
data Command
= Create String
| Delete
The real magic: mysubparser wraps hsubparser to add global options and deal with merging them. It takes the parser for global options as an argument:
mysubparser :: forall a b. Monoid a
=> Parser a
-> Mod CommandFields b
-> Parser (a, b)
mysubparser globals cmds = do
To start with, it runs the global parser (to catch any globals given before a command):
g1 <- globals
It then uses hsubparser to get a command parser, and modifies it to also parse global options:
(g2, r) <- addGlobals $ hsubparser cmds
Finally, it merges the two global option sets, and returns the parsed global options and the command parser result:
pure (g1 <> g2, r)
where
The addGlobals helper function:
addGlobals :: forall c. Parser c -> Parser (a, c)
If NilP was given, we just use mempty to get the default option set:
addGlobals (NilP x) = NilP $ (mempty,) <$> x
The important case: if we have an OptP around an Option that uses a CommandReader, the globals parser is added to every command parser:
addGlobals (OptP (Option (CmdReader n cs g) ps)) =
OptP (Option (CmdReader n cs $ fmap go . g) ps)
where go pi = pi { infoParser = (,) <$> globals <*> infoParser pi }
In all other cases, either just use the default option set, or merge option sets from recursive Parsers as appropriate:
addGlobals (OptP o) = OptP ((mempty,) <$> o)
addGlobals (AltP p1 p2) = AltP (addGlobals p1) (addGlobals p2)
addGlobals (MultP p1 p2) =
MultP ((\(g2, f) -> \(g1, x) -> (g1 <> g2, f x)) <$> addGlobals p1)
(addGlobals p2)
addGlobals (BindP p k) = BindP (addGlobals p) $ \(g1, x) ->
BindP (addGlobals $ k x) $ \(g2, x') ->
pure (g1 <> g2, x')
Modifications to the main function are fairly minimal, and mostly related to using the new GlobalOpts. Once a parser for GlobalOpts is available, passing it to mysubparser is quite easy:
main :: IO ()
main = do
(opts :: Opts) <- execParser optsParser
case optCommand opts of
Create name -> putStrLn ("Created the thing named " ++ name)
Delete -> putStrLn "Deleted the thing!"
putStrLn ("global flag: " ++ show (optGlobalFlag (optGlobals opts)))
where
optsParser :: ParserInfo Opts
optsParser =
info
(helper <*> programOptions)
(fullDesc <> progDesc "optparse subcommands example" <>
header
"optparse-sub-example - a small example program for optparse-applicative with subcommands")
versionOption :: Parser (a -> a)
versionOption = infoOption "0.0" (long "version" <> help "Show version")
globalOpts :: Parser GlobalOpts
globalOpts = versionOption <*>
(GlobalOpts <$> switch (long "global-flag" <> help "Set a global flag"))
programOptions :: Parser Opts
programOptions =
uncurry Opts <$> mysubparser globalOpts (createCommand <> deleteCommand)
createCommand :: Mod CommandFields Command
createCommand =
command
"create"
(info createOptions (progDesc "Create a thing"))
createOptions :: Parser Command
createOptions =
Create <$>
strArgument (metavar "NAME" <> help "Name of the thing to create")
deleteCommand :: Mod CommandFields Command
deleteCommand =
command
"delete"
(info (pure Delete) (progDesc "Delete the thing"))
Notice that mysubparser should be a quite generic/reusable component.
This exhibits behavior closer to what you wanted:
$ ./optparse-sub-example create --global-flag HI
Created the thing named HI
global flag: True
$ ./optparse-sub-example --global-flag create HI
Created the thing named HI
global flag: True
$ ./optparse-sub-example --global-flag delete
Deleted the thing!
global flag: True
$ ./optparse-sub-example delete --global-flag
Deleted the thing!
global flag: True
$ ./optparse-sub-example delete
Deleted the thing!
global flag: False
$ ./optparse-sub-example delete --version
0.0
$ ./optparse-sub-example create --version
0.0
I'm doing my first steps using Haskell. I created a project using stack and changed the Main.hs into
module Main where
my_fkt :: String -> String
my_fkt input = show (length input)
main :: IO ()
main = interact my_fkt
I build the project via stack build, run it via stack exec firststeps-exe, enter "abcd" and finish input via <CTRL>-D. In the console I now see
abcd4%
The %is inverted. If I use a text file containing the "abcd" (without line break) and execute more sample.txt | stack exec firststeps-exe I see
abcd5%
Why do I get one additional character in the second case and what is the inverted percentage sign?
That is because the definition of interact uses putStr instead of putStrLn.
You can take a look at the source code here.
interact :: (String -> String) -> IO ()
interact f = do s <- getContents
putStr (f s)
To remedy your issue I would go on and create a similar function
interact' :: (String -> String) -> IO ()
interact' f = do s <- getContents
putStrLn (f s)
or if you like to mix it up and write a bit terser code
interact' f = putStrLn =<< (f <$> getContents)
I don't know what the % is or why it is showing up, my guess would be that it is the escaped CTRL-D.
With regards to your second question about the additional "non-existing" character, I am also not sure, but here my guess would be that this is the \EOF.
Btw. you can always check using more testinput | wc -c it should yield the same result as your haskell program.
I'm approaching Haskell with a view of converting runtime errors to compile-time errors. I expect the compiler to figure out that all the code paths in the following code are not error free. If authorizeUser returns UnauthorizedCommand then the call to (command $ authorizeUser cmd userId) will fail during runtime.
data Command = UnauthorizedCommand | Command {command :: String, userId :: String}
authorizedUsers = ["1", "2", "3"]
authorizeUser :: String -> String -> Command
authorizeUser cmd userId = if (userId `elem` authorizedUsers) then Command {command=cmd, userId=userId} else UnauthorizedCommand
main :: IO ()
main = do
cmd <- getLine
userId <- getLine
case (command $ authorizeUser cmd userId) of
"ls" -> putStrLn "works"
_ -> putStrLn "not authorized"
Is there a compiler switch to enable such checking?
The main problem in this example is that using the record syntax in a data type with multiple constructors can end up with record selector functions that are partial. It's therefore not recommended to ever mix records and multiple constructors unless all constructors have identical record fields.
The simplest solution is to not use record syntax here and to define your own command accessor which makes the error case explicit with a Maybe return type:
data Command = UnauthorizedCommand | Command String String
command :: Command -> Maybe String
command UnauthorizedCommand = Nothing
command (Command cmd _) = Just cmd
A slightly more complex way to check this during compile time is to use GADTs to encode the permission level in the types.
{-# LANGUAGE GADTs #-}
{-# LANGUAGE DataKinds #-}
data Permission
= Authorized | Unauthorized
data Command p where
UnauthorizedCommand :: Command Unauthorized
Command :: {command :: String, userId :: String} -> Command Authorized
Now the type of the command selector is Command Authorized -> String so you get a compile time error if you try to use it with an unauthorized command.
You can use the "case" only on authorizeUser and then filter the DATA Command
data Command = UnauthorizedCommand | Command {command :: String, userId :: String}
authorizedUsers = ["1", "2", "3"]
authorizeUser :: String -> String -> Command
authorizeUser cmd userId = if (userId `elem` authorizedUsers) then Command {command=cmd, userId=userId} else UnauthorizedCommand
main :: IO ()
main = do
cmd <- getLine
userId <- getLine
case authorizeUser cmd userId of
Command "ls" _ -> putStrLn "works"
_ -> putStrLn "not authorized" -- either the user id is not good or the command is not good.
If you have several command, you can also do:
main = do
cmd <- getLine
userId <- getLine
case authorizeUser cmd userId of
Command _ _ -> case cmd of
"ls" -> putStrLn "works"
"cd" -> putStrLn "moved!"
_ -> putStrLn "Bad command!"
_ -> putStrLn "not authorized" -- or: Unauthorized -> putStrLn "Not authorized"
PS : Note you can factor the putStrLns
I think I've finally understand your question: you want Haskell generate error/warning when calling command on an arbitrary Command. It is related to this answer : https://stackoverflow.com/a/3804900/3421913 . In short:
It is not possible to check (or very very hard) at compile time if a command is Command Str Str or Unauthorized: in your case it is quite simple, but it might be more complex.
You sometimes know that your value has the good property but it's difficult to express that in haskell (see the linked answer above with the duplicates/remove_duplicates functions). In your case, authorizeUser cmd userId is unsafe (since cmd and userId are user input), so we won't use the command function which will fail if the user is a monkey. Instead, we'll use the pattern matching I've proposed in my other answer.
If you have some invariant ensuring that c::Command is not Unauthorized, you might use directly command.
I'm trying to write a really simple editor like "ed".
In this program I'm trying to use a mapping for building the control which translate string-command in actions to perform.
Here's a piece of code:
commands :: Map String ([Handle] -> IO ())
commands = fromAscList [
("o",\list -> print "Insert name of the file to be opened" >> getLine >>= \nomefile ->
openFile nomefile ReadWriteMode >>= \handle -> editor (handle:list)),
("i",\list -> case list of { [] -> print "No buffer open" ; handle:res -> write handle } >> editor list),
("q",\list -> if list == [] then return () else mapM_ hClose list >> return ())
]
editor :: [Handle] -> IO()
editor list = do
command <- getLine
let action = lookup command commands
case action of
Nothing -> print "Unknown command" >> editor list
Just act -> act list
The problem is that when I execute the editor function, either in ghci or in an executable file, when I type "o" I get the message "Unknown command" instead of the call to the function to open the file. I've tryed the same code using associative list instead of Map and in this case it works. So what could be the problem here?
What's more odd is that if I call keys on the mapping commands in ghci it return a list containing the string "o" too.
I thank in advance for any help.
commands :: Map String ([Handle] -> IO ())
commands = fromAscList [
("o",_),
("i",_),
("q",_)
]
But
ghci> Data.List.sort ["o","i","q"]
["i","o","q"]
You were lying to Data.Map, so it constructed a Map that didn't satisfy the required invariants. Thus looking up things in the Map didn't work, since the request was sent down the wrong branch (sometimes).