on master help sign in
Re #195: yaml2json --help --version options

Using optparse-applicative, let yaml2json understand options --help | -h --version | -V | --numeric-version in a robust way. Otherwise, no change of cmdline syntax: yaml2json file.yml # read from file yaml2json - # read from stdin Tested build with GHC 8.0 - 9.0.

commit0722db0366f52cb5560389aa1a5c562c8fe76e37 authorAndreas Abel <andreas.abel@ifi.lmu.de> dateTue 14 Sep 2021, 15:30 (5 years ago) parente0f3d35
3 files changed+95−20
yaml/exe/yaml2json.hs+85−20 yaml/package.yaml+5 yaml/yaml.cabal+5
yaml/exe/yaml2json.hs+85−20
@@ -1,31 +1,96 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE LambdaCase #-}
3+{-# LANGUAGE NamedFieldPuns #-}
4+
15import Prelude hiding (putStr, getContents)
26
3−import Data.Aeson (encode, Value)
4−import Data.ByteString (getContents)
7+import Data.Aeson (encode, Value)
8+import Data.ByteString (getContents)
59import Data.ByteString.Lazy (putStr)
6−import System.Environment (getArgs)
10+import Data.List (intercalate)
11+#if !MIN_VERSION_base(4,11,0)
12+import Data.Semigroup (Semigroup(..))
13+#endif
14+import Data.Version (Version(versionBranch))
15+import Data.Yaml (decodeFileEither, decodeEither')
16+
17+import Options.Applicative
18+ ( Parser
19+ , action, execParser, header, help, helper, hidden
20+ , info, infoOption, long, metavar, short, strArgument
21+ )
22+
723import System.Exit
8−import System.IO (stderr, hPutStrLn)
24+import System.IO (stderr, hPrint)
25+
26+import qualified Paths_yaml as Paths
27+
28+newtype Options = Options
29+ { optInput :: Maybe FilePath
30+ -- ^ Nothing means @stdin@.
31+ }
32+
33+-- | Name of the executable.
34+
35+self :: String
36+self = "yaml2json"
37+
38+-- | Homepage for this program.
939
10−import Data.Yaml (decodeFileEither, decodeEither')
40+homepage :: String
41+homepage = "https://github.com/snoyberg/yaml/"
1142
12−helpMessage :: IO ()
13−helpMessage = putStrLn "Usage: yaml2json FILE\n\nuse '-' as FILE to indicate stdin" >> exitFailure
43+-- | Version in @x.y.z@ format.
44+
45+version :: String
46+version = intercalate "." $ map show $ versionBranch Paths.version
47+
48+-- | Parse options; handle parse errors, @--help@, @--version@.
49+
50+options :: IO Options
51+options =
52+ execParser $
53+ info (helper <*> versionOption <*> numericVersionOption <*> programOptions)
54+ (header "Reads given YAML document and prints it JSON format to standard out.")
55+
56+ where
57+ versionOption =
58+ infoOption (unwords versionWords)
59+ $ long "version"
60+ <> short 'V'
61+ <> hidden
62+ <> help "Show version info."
63+ versionWords = [ self, "version", version, homepage ]
64+
65+ numericVersionOption =
66+ infoOption version
67+ $ long "numeric-version"
68+ <> hidden
69+ <> help "Show just version number."
70+
71+ programOptions = Options <$> oInput
72+
73+ oInput :: Parser (Maybe FilePath)
74+ oInput = dashToStdin <$> do
75+ strArgument
76+ $ metavar "FILE"
77+ <> action "file"
78+ <> help "The input file containing the YAML document; use '-' for stdin."
79+ where
80+ -- dash is interpreted as stdin
81+ dashToStdin = \case
82+ "-" -> Nothing
83+ file -> Just file
1484
1585showJSON :: Show a => Either a Value -> IO b
16−showJSON ejson =
17− case ejson of
18− Left err -> hPutStrLn stderr (show err) >> exitFailure
19− Right res -> putStr (encode (res :: Value)) >> exitSuccess
86+showJSON = \case
87+ Left err -> hPrint stderr err >> exitFailure
88+ Right res -> putStr (encode (res :: Value)) >> exitSuccess
2089
2190main :: IO ()
2291main = do
23− args <- getArgs
24− case args of
25− -- strict getContents will read in all of stdin at once
26− (["-h"]) -> helpMessage
27− (["--help"]) -> helpMessage
28− (["-"]) -> getContents >>= showJSON . decodeEither'
29− ([f]) -> decodeFileEither f >>= showJSON
30− _ -> helpMessage
31−
92+ Options{ optInput } <- options
93+ showJSON =<< case optInput of
94+ -- strict getContents will read in all of stdin at once
95+ Nothing -> decodeEither' <$> getContents
96+ Just f -> decodeFileEither f
yaml/package.yaml+5
@@ -72,10 +72,15 @@ executables:
7272 main: yaml2json.hs
7373 source-dirs: exe
7474 dependencies:
75+ - optparse-applicative
7576 - yaml
7677 when:
7778 - condition: flag(no-exe)
7879 buildable: false
80+ other-extensions:
81+ - CPP
82+ - LambdaCase
83+ - NamedFieldPuns
7984 examples:
8085 main: Main.hs
8186 source-dirs: examples
yaml/yaml.cabal+5
@@ -154,6 +154,10 @@ executable yaml2json
154154 Paths_yaml
155155 hs-source-dirs:
156156 exe
157+ other-extensions:
158+ CPP
159+ LambdaCase
160+ NamedFieldPuns
157161 ghc-options: -Wall -Wcompat
158162 build-depends:
159163 aeson >=0.11
@@ -166,6 +170,7 @@ executable yaml2json
166170 , filepath
167171 , libyaml ==0.1.*
168172 , mtl
173+ , optparse-applicative
169174 , resourcet >=0.3 && <1.3
170175 , scientific >=0.3
171176 , template-haskell