on master help sign in
Merge pull request #197 from andreasabel/optparse-applicative

Fix #195: options --help and --version

commit2ba7d843ddac894000c708cc8995dc036f2e625b authorMichael Snoyman <michael@snoyman.com> dateWed 15 Sep 2021, 06:29 (5 years ago) parent6bbb9df0722db0
3 files changed+112−55
yaml/exe/yaml2json.hs+85−20 yaml/package.yaml+9−8 yaml/yaml.cabal+18−27
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+9−8
@@ -34,7 +34,7 @@ flags:
3434 default: true
3535
3636dependencies:
37−- base >=4.9.1 && <5 # GHC 8.0.2 and later
37+- base >=4.9.1 && <5 # GHC 8.0.2 and later, meaning that Data.Semigroup is in base
3838- bytestring >=0.9.1.4
3939- transformers >=0.1
4040- mtl
@@ -51,15 +51,12 @@ dependencies:
5151- directory
5252- template-haskell
5353- libyaml >=0.1 && <0.2
54−when:
55− condition: "!impl(ghc >= 8.0)"
56− dependencies:
57− - semigroups
54+
55+ghc-options: -Wall -Wcompat
5856
5957library:
6058 other-extensions:
6159 - LambdaCase
62− ghc-options: -Wall
6360 source-dirs: src
6461
6562executables:
@@ -75,14 +72,18 @@ executables:
7572 main: yaml2json.hs
7673 source-dirs: exe
7774 dependencies:
75+ - optparse-applicative
7876 - yaml
7977 when:
8078 - condition: flag(no-exe)
8179 buildable: false
80+ other-extensions:
81+ - CPP
82+ - LambdaCase
83+ - NamedFieldPuns
8284 examples:
8385 main: Main.hs
8486 source-dirs: examples
85− ghc-options: -Wall
8687 when:
8788 - condition: flag(no-examples)
8889 then:
@@ -96,7 +97,7 @@ tests:
9697 spec:
9798 main: Spec.hs
9899 source-dirs: test
99− ghc-options: -Wall "-with-rtsopts=-K1K"
100+ ghc-options: "-with-rtsopts=-K1K"
100101 cpp-options: -DTEST
101102 dependencies:
102103 - hspec >=1.3
yaml/yaml.cabal+18−27
@@ -1,10 +1,8 @@
11cabal-version: 1.12
22
3−-- This file has been generated from package.yaml by hpack version 0.33.0.
3+-- This file has been generated from package.yaml by hpack version 0.34.4.
44--
55-- see: https://github.com/sol/hpack
6−--
7−-- hash: 55c85c8d4d3074a558a82e30a2592ecff9db2e6f1571547c73d26ba44bfc1c20
86
97name: yaml
108version: 0.11.5.0
@@ -62,8 +60,9 @@ library
6260 Paths_yaml
6361 hs-source-dirs:
6462 src
65− other-extensions: LambdaCase
66− ghc-options: -Wall
63+ other-extensions:
64+ LambdaCase
65+ ghc-options: -Wall -Wcompat
6766 build-depends:
6867 aeson >=0.11
6968 , attoparsec >=0.11.3.0
@@ -73,7 +72,7 @@ library
7372 , containers
7473 , directory
7574 , filepath
76− , libyaml >=0.1 && <0.2
75+ , libyaml ==0.1.*
7776 , mtl
7877 , resourcet >=0.3 && <1.3
7978 , scientific >=0.3
@@ -82,9 +81,6 @@ library
8281 , transformers >=0.1
8382 , unordered-containers
8483 , vector
85− if !impl(ghc >= 8.0)
86− build-depends:
87− semigroups
8884 default-language: Haskell2010
8985
9086executable examples
@@ -95,7 +91,7 @@ executable examples
9591 Paths_yaml
9692 hs-source-dirs:
9793 examples
98− ghc-options: -Wall
94+ ghc-options: -Wall -Wcompat
9995 build-depends:
10096 aeson >=0.11
10197 , attoparsec >=0.11.3.0
@@ -105,7 +101,7 @@ executable examples
105101 , containers
106102 , directory
107103 , filepath
108− , libyaml >=0.1 && <0.2
104+ , libyaml ==0.1.*
109105 , mtl
110106 , resourcet >=0.3 && <1.3
111107 , scientific >=0.3
@@ -114,9 +110,6 @@ executable examples
114110 , transformers >=0.1
115111 , unordered-containers
116112 , vector
117− if !impl(ghc >= 8.0)
118− build-depends:
119− semigroups
120113 if flag(no-examples)
121114 buildable: False
122115 else
@@ -131,6 +124,7 @@ executable json2yaml
131124 Paths_yaml
132125 hs-source-dirs:
133126 exe
127+ ghc-options: -Wall -Wcompat
134128 build-depends:
135129 aeson >=0.11
136130 , attoparsec >=0.11.3.0
@@ -140,7 +134,7 @@ executable json2yaml
140134 , containers
141135 , directory
142136 , filepath
143− , libyaml >=0.1 && <0.2
137+ , libyaml ==0.1.*
144138 , mtl
145139 , resourcet >=0.3 && <1.3
146140 , scientific >=0.3
@@ -150,9 +144,6 @@ executable json2yaml
150144 , unordered-containers
151145 , vector
152146 , yaml
153− if !impl(ghc >= 8.0)
154− build-depends:
155− semigroups
156147 if flag(no-exe)
157148 buildable: False
158149 default-language: Haskell2010
@@ -163,6 +154,11 @@ executable yaml2json
163154 Paths_yaml
164155 hs-source-dirs:
165156 exe
157+ other-extensions:
158+ CPP
159+ LambdaCase
160+ NamedFieldPuns
161+ ghc-options: -Wall -Wcompat
166162 build-depends:
167163 aeson >=0.11
168164 , attoparsec >=0.11.3.0
@@ -172,8 +168,9 @@ executable yaml2json
172168 , containers
173169 , directory
174170 , filepath
175− , libyaml >=0.1 && <0.2
171+ , libyaml ==0.1.*
176172 , mtl
173+ , optparse-applicative
177174 , resourcet >=0.3 && <1.3
178175 , scientific >=0.3
179176 , template-haskell
@@ -182,9 +179,6 @@ executable yaml2json
182179 , unordered-containers
183180 , vector
184181 , yaml
185− if !impl(ghc >= 8.0)
186− build-depends:
187− semigroups
188182 if flag(no-exe)
189183 buildable: False
190184 default-language: Haskell2010
@@ -199,7 +193,7 @@ test-suite spec
199193 Paths_yaml
200194 hs-source-dirs:
201195 test
202− ghc-options: -Wall "-with-rtsopts=-K1K"
196+ ghc-options: -Wall -Wcompat -with-rtsopts=-K1K
203197 cpp-options: -DTEST
204198 build-depends:
205199 HUnit
@@ -213,7 +207,7 @@ test-suite spec
213207 , directory
214208 , filepath
215209 , hspec >=1.3
216− , libyaml >=0.1 && <0.2
210+ , libyaml ==0.1.*
217211 , mockery
218212 , mtl
219213 , raw-strings-qq
@@ -226,7 +220,4 @@ test-suite spec
226220 , unordered-containers
227221 , vector
228222 , yaml
229− if !impl(ghc >= 8.0)
230− build-depends:
231− semigroups
232223 default-language: Haskell2010