Is it possible to have both positional and named CLI options using optparse-generic? - haskell

I'd like to make a CLI using the Haskell library optparse-generic. The documentation shows an example for positional arguments, and another for options (e.g. --flag=True), but I can't figure out how to combine the two. I've tried something like this:
data CLI = CLI Options Files deriving (Generic, Show)
data Files = Files [String] deriving (Generic, Show)
data Options = Options { colorMap :: String <?> "Color mapping to use."
} deriving (Generic, Show)
instance ParseRecord CLI
But that doesn't seem to be legal.

Related

Haskell. Why does read function does not work with custom data type though I used deriving?

Read function does not work properly with custom data type.
data TwoInts = Int Int
deriving (Read, Show)
conv :: String -> TwoInts
conv s = read s
When i load this function with ghci
ghci conv.hs
It loads properly, but when i call it
conv "15 14"
i am getting following error:
*** Exception: Prelude.read: no parse
As suggested, your data type:
data TwoInts = Int Int
deriving (Read, Show)
describes a constructor named Int that accepts a type Int. If you were to use records, maybe this is more clear:
data TwoInts = Int { int :: Int}
deriving (Read, Show)
The solution as suggested in the comments is to name your constructor something else, e.g.,
data TwoInts = TwoInts Int Int
deriving (Read, Show)

Multiple declaration error in data type declaration

I'm currently building a a Twitter CLI client in Haskell, and I have a data type that represents a DM and one that represents a tweet. However, I get a multiple declaration error because I have to use the same name for both:
data Users = Users { screen_name :: String } deriving(Show, Generic)
data Tweet = Tweet { text :: !Text,
retweeted :: Bool,
user :: Users
} deriving (Show, Generic)
data DM = DM { text :: !Text,
sender_screen_name :: String
} deriving (Show, Generic)
Does someone know a solution for this particular problem?
As defined here, the named members are just functions that are used to call the values in your data structure.
So, if you really want to use them, you can do so by using the language extension. You can do that by declaring this in your file:
{-# LANGUAGE DuplicateRecordFields #-}

Haskell, unnamed command line arguments for optparse-generic

I'm using optparse-generic to parse the command line arguments of a program called example. I have a datatype with named fields (record syntax). For example:
data Example = Example { foo :: Int, bar :: String } deriving (Generic, Show)
This generates a program which can be called as follows:
./example --foo 42 --bar "baz"
How can I tell optparse-generic that bar should be an unnamed, mandatory, positional command line argument. That means, I don't want to type --bar when I call example. For example, I want to call example the following:
./example --foo 42 "baz"
optparse-generic does not support generating such a parser from a single data type definition since Haskell does not support records with both labeled and unlabeled fields.
However, what you can do is generate one data type for all the labeled fields and one type for the unlabeled fields and then combine them using Applicative operations, like this:
data Labeled = Labeled { foo :: Int } deriving (Generic, Show)
instance ParseRecord Labeled
data Unlabeled = Unlabeled String deriving (Generic, Show)
instance ParseRecord Unlabeled
data Mixed = Mixed Labeled Unlabeled deriving (Show)
instance ParseRecord Mixed where
parseRecord = Mixed <$> parseRecord <*> parseRecord

Haskell, documentation for unlabeled command line arguments in optparse-generic

I'm using optparse-generic to parse the command line arguments of a program called example. I have a datatype with an unnamed field. For example:
data Unlabeled = Unlabeled String deriving (Generic, Show)
This generates a program which can be called as follows: ./exmaple "foo". However, for the user, there is no documentation what the String-parameter is about. In particular, ./example --help does not give any valuable information about this positional String argument ./example expects.
Using named datatypes (record syntax), it is possible to add documentation to the datatype. For example
data Labeled = Labeled {name :: String <?> "Select the foo"} deriving (Generic, Show)
This generates help text for the program. For example, when called ./example --help, it will display --name STRING Select the foo.
How can I add documentation to unnamed datatypes in the same way as I can do for record-syntax-datatypes?
data Labeled = Labeled (String <?> "Select the foo") will give you
...
STRING Select the foo
...
in the --help message. To perhaps clarify, the <?> is simply a type constructor, it is just syntactically an operator. Maybe fun fact: you can write data X = X (Int `Either` Bool) as well.

Using Aeson generics to construct JSON with a value as key holding another value

Toying a bit with the github gist API while trying to get down with the Aeson JSON library. I've run into a problem with the generated ToJSON instance, and I don't know exactly how to solve it.
I need to contain a value inside and the key that is associated to the value also needs to be a value and not a predefined key name. It's a bit easier to show. The desired output is,
{
"public": true,
"description": "Something..",
"files": {"This Thing.md": {"content": "Here we go!"}}
}
where the value of the filename is holding the content, but currently I get,
{
"public": true,
"description": "Something..",
"files": {"filename": "This Thing.md", "content": "Here we go!"}
}
Which isn't really what I need. The current code is,
{-# LANGUAGE OverloadedStrings, DeriveGeneric #-}
import Data.Text (Text)
import Data.Aeson
import GHC.Generics
data GistContent = GistContent
{ filename :: Text
, content :: Text
} deriving (Show, Generic)
instance ToJSON GistContent
data Gist = Gist
{ description :: Text
, public :: Bool
, files :: GistContent
} deriving (Show, Generic)
instance ToJSON Gist
Under the assumption that it is possible, how would my datastructure need to look to get the desired output?.. And if that's not possible using the generics, how'd I got about it using the ToJSON instance (I can't quite figure out the structure there either)?
Your problem stems from an incorrect schema. files can currently only contain one GistContent, which is unnecessarily limiting. Instead, you'd want to have a list of GistContents:
data Gist = Gist
{ description :: Text
, public :: Bool
, files :: [GistContent]
} deriving (Show, Generic)
Now consider another constraint on Gist: each GistContent must have a different filename. A data structure that would enforce this would be Data.HashMap.Strict.HashMap. Taking the filename out of GistContent and using the filename as a key:
data GistContent = GistContent
{ content :: Text
} deriving (Show, Generic)
data Gist = Gist
{ description :: Text
, public :: Bool
, files :: HashMap Text GistContent
} deriving (Show, Generic)
Everything works out.
Here's the manually written instance (see the documentation for the class):
instance ToJSON GistContent where
toJSON (GistContent { filename = f, content = c }) = object [f .= c]
I doubt if there would be any way to get this with your existing datatype with the automatically generated instances because all they can do is to follow the datatype using a standard scheme. Note that you can still use the generic instance for Gist because that will call the (non-generic) instance for GistContent.

Resources