This repository has been archived by the owner on Apr 26, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 9
/
Docopt.purs
186 lines (165 loc) · 5.41 KB
/
Docopt.purs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
-- |
-- | Docopt utility surface.
-- |
-- | The impure part of docopt, providing conventient entry
-- | points and functions to use docopt.
-- |
module Docopt (
run
, parse
, defaultOptions
, DocoptEff ()
, Options (..)
, Argv ()
) where
import Prelude
import Debug.Trace
import Control.Monad.Aff (Aff)
import Control.Monad.Eff.Exception (error, throwException, EXCEPTION())
import Control.Applicative (liftA1)
import Data.Tuple (Tuple(..))
import Data.Tuple.Nested ((/\))
import Data.Foldable (any, intercalate)
import Data.Either (Either(..), either)
import Control.Monad.Eff (Eff())
import Data.Maybe (Maybe(..), maybe, fromMaybe)
import Node.FS (FS())
import Node.Process (PROCESS())
import Node.Process as Process
import Control.Monad.Eff.Class (liftEff)
import Control.Monad.Eff.Console (CONSOLE)
import Control.Monad.Eff.Console as Console
import Text.Wrap (dedent)
import Data.StrMap (StrMap())
import Data.Array as A
import Data.StrMap (member)
import Data.Bifunctor (lmap, bimap)
import Data.String.Yarn (lines, unlines)
import Data.String (trim) as String
import Language.Docopt.Errors (developerErrorMessage)
import Language.Docopt (Docopt, parseDocopt, evalDocopt)
import Language.Docopt.Value (Value())
import Language.Docopt as D
import Language.Docopt.Env (Env())
type Argv = Array String
type DocoptEff e = ( process :: PROCESS
, err :: EXCEPTION
, console :: CONSOLE
, fs :: FS
| e
)
liftEffA :: ∀ e a. Eff (DocoptEff e) a -> Aff (DocoptEff e) a
liftEffA = liftEff
-- |
-- | Options for a docopt run
-- |
type ParseOptionsObj r = {
smartOptions :: Boolean -- ^ parse singleton groups as opts if possible
| r
}
type Options r = {
argv :: Maybe Argv -- ^ override argv. Defaults to `process.argv`
, env :: Maybe Env -- ^ override env. Defaults to `process.env`
, optionsFirst :: Boolean -- ^ enable "option-first"
, dontExit :: Boolean -- ^ don't exit the process upon failure
, smartOptions :: Boolean -- ^ parse singleton groups as opts if possible
, stopAt :: Array String -- ^ stop parsing at these custom EOA markers
, requireFlags :: Boolean -- ^ do not ignore missing flags
, laxPlacement :: Boolean -- ^ allow positionals/commands to be appear anywhere
, version :: Maybe String -- ^ the version string to display
, versionFlags :: Array String -- ^ list of flags that trigger 'version'
, helpFlags :: Array String -- ^ list of flags that trigger 'help'
}
defaultOptions :: Options {}
defaultOptions = {
argv: Nothing
, env: Nothing
, optionsFirst: false
, dontExit: false
, smartOptions: false
, stopAt: []
, laxPlacement: false
, requireFlags: false
, version: Nothing
, versionFlags: [ "--version" ]
, helpFlags: [ "--help" ]
}
-- |
-- | Parse the docopt specification from the given help text.
-- |
parse :: ∀ e r
. String
-> ParseOptionsObj r
-> Eff (DocoptEff e) Docopt
parse helpText opts = do
either (throwException <<< error) pure do
parseDocopt helpText opts
data Action a
= ShowHelp String
| ShowVersion
| Return a
-- |
-- | Run docopt on the given help text.
-- |
-- | This either succeeds with the key/value mappings or fails with a
-- | descriptive help message.
-- |
run :: ∀ e r
. Either Docopt String
-> Options r
-> Eff (DocoptEff e) (StrMap Value)
run input opts = do
argv <- maybe (A.drop 2 <$> Process.argv) pure opts.argv
env <- maybe Process.getEnv pure opts.env
program /\ action <- runEither do
{ program, specification, shortHelp, help } <- case input of
(Left spec) -> pure spec
(Right help') -> parseDocopt help' opts
bimap
(fmtHelp program opts.helpFlags shortHelp)
((program /\ _) <<< case _ of
output | canExit && output `has` opts.helpFlags -> ShowHelp help
output | canExit && output `has` opts.versionFlags -> ShowVersion
output -> Return output
)
(evalDocopt program specification env argv opts)
case action of
Return v -> pure v
ShowHelp help -> abort 0 (String.trim help)
ShowVersion -> do
mVer <- maybe readPkgVersion (pure <<< pure) opts.version
case mVer of
Just ver -> abort 0 ver
Nothing -> abort 1
$ program
<> ": version not detected."
<> "\n"
<> developerErrorMessage
abort 0 ""
where
has x = any (_ `member` x)
canExit = not opts.dontExit
-- note: purescript needs the `a` for now:
abort :: ∀ a. _ -> _ -> _ _ a
abort _ msg | opts.dontExit = throwException (error msg)
abort code msg
= let log = if code == 0 then Console.log else Console.error
in do
log msg
Process.exit code
runEither = flip either pure (abort 1)
readPkgVersion = readPkgVersionImpl Just Nothing
fmtHelp program helpFlags shortHelp errmsg
= errmsg
<> "\n"
<> (dedent $ unlines $ (" " <> _) <$> lines (dedent shortHelp))
<> if A.length helpFlags == 0
then ""
else "\n" <> "See "
<> program <> " " <> (intercalate "/" helpFlags)
<> " for more information"
foreign import readPkgVersionImpl
:: ∀ e
. (String -> Maybe String)
-> Maybe String
-> Eff e (Maybe String)