initial commit
commit
4f93f79f5f
|
@ -0,0 +1,13 @@
|
|||
*.glade~
|
||||
*.prof
|
||||
*.aux
|
||||
*.eventlog
|
||||
*.hp
|
||||
*.ps
|
||||
/*.pdf
|
||||
dist
|
||||
.cabal-sandbox/
|
||||
.stack-work/
|
||||
cabal.sandbox.config
|
||||
**/*.gui~
|
||||
\#*.gui\#
|
|
@ -0,0 +1,5 @@
|
|||
# Revision history for cmdparse-applicative
|
||||
|
||||
## 0.1.0.0 -- YYYY-mm-dd
|
||||
|
||||
* First version. Released on an unsuspecting world.
|
|
@ -0,0 +1,30 @@
|
|||
Copyright (c) 2016, Lennart Spitzner
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
* Neither the name of Lennart Spitzner nor the names of other
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,69 @@
|
|||
-- Initial cmdparse-applicative.cabal generated by cabal init. For further
|
||||
-- documentation, see http://haskell.org/cabal/users-guide/
|
||||
|
||||
name: cmdparse-applicative
|
||||
version: 0.1.0.0
|
||||
-- synopsis:
|
||||
-- description:
|
||||
license: BSD3
|
||||
license-file: LICENSE
|
||||
author: Lennart Spitzner
|
||||
maintainer: lsp@informatik.uni-kiel.de
|
||||
-- copyright:
|
||||
-- category:
|
||||
build-type: Simple
|
||||
extra-source-files: ChangeLog.md
|
||||
cabal-version: >=1.10
|
||||
|
||||
flag cmdparse-applicative-dev
|
||||
description: dev options
|
||||
default: False
|
||||
|
||||
library
|
||||
exposed-modules: UI.CmdParse.Applicative.Types
|
||||
UI.CmdParse.Applicative
|
||||
UI.CmdParse.Monadic.Types
|
||||
UI.CmdParse.Monadic
|
||||
-- other-modules:
|
||||
-- other-extensions:
|
||||
build-depends:
|
||||
{ base >=4.9 && <4.10
|
||||
, free
|
||||
, unsafe
|
||||
, lens
|
||||
, qualified-prelude
|
||||
, multistate
|
||||
, pretty
|
||||
, containers
|
||||
, either
|
||||
, transformers
|
||||
, mtl
|
||||
}
|
||||
hs-source-dirs: src
|
||||
default-language: Haskell2010
|
||||
default-extensions: {
|
||||
CPP
|
||||
|
||||
NoImplicitPrelude
|
||||
|
||||
GADTs
|
||||
|
||||
FlexibleContexts
|
||||
FlexibleInstances
|
||||
ScopedTypeVariables
|
||||
MonadComprehensions
|
||||
LambdaCase
|
||||
MultiWayIf
|
||||
KindSignatures
|
||||
ApplicativeDo
|
||||
}
|
||||
ghc-options: {
|
||||
-Wall
|
||||
-fprof-auto -fprof-cafs -fno-spec-constr
|
||||
-j
|
||||
-fno-warn-unused-imports
|
||||
-fno-warn-orphans
|
||||
}
|
||||
if flag(cmdparse-applicative-dev) {
|
||||
ghc-options: -O0 -Werror
|
||||
}
|
|
@ -0,0 +1,628 @@
|
|||
{-# LANGUAGE DeriveFunctor #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE ExistentialQuantification #-}
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
{-# LANGUAGE StandaloneDeriving #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE TypeOperators #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
{-# LANGUAGE MonadComprehensions #-}
|
||||
{-# LANGUAGE MultiWayIf #-}
|
||||
|
||||
module UI.CmdParse.Applicative
|
||||
( -- main
|
||||
cmdActionPartial
|
||||
, cmdAction
|
||||
, cmd_run
|
||||
, CmdBuilder
|
||||
, addFlag
|
||||
, addCmd
|
||||
, addParam
|
||||
, help
|
||||
, impl
|
||||
, def
|
||||
, flagAsBool
|
||||
, cmdGetPartial
|
||||
, ppCommand
|
||||
, ppCommandShort
|
||||
, ppCommandShortHelp
|
||||
-- re-exports:
|
||||
, Command(..)
|
||||
, Flag(..)
|
||||
, Param(..)
|
||||
, cmdCheckNonStatic
|
||||
)
|
||||
where
|
||||
|
||||
|
||||
|
||||
#include "qprelude/bundle-gamma.inc"
|
||||
import Control.Applicative.Free
|
||||
import qualified Control.Monad.Trans.MultiRWS.Strict as MultiRWSS
|
||||
import qualified Control.Monad.Trans.MultiState.Strict as MultiStateS
|
||||
import Data.Unique (Unique)
|
||||
import qualified System.Unsafe as Unsafe
|
||||
|
||||
import qualified Control.Lens.TH as LensTH
|
||||
import qualified Control.Lens as Lens
|
||||
import Control.Lens ( (.=), (%=), (%~), (.~) )
|
||||
|
||||
import qualified Text.PrettyPrint as PP
|
||||
|
||||
import UI.CmdParse.Applicative.Types
|
||||
|
||||
import Data.HList.ContainsType
|
||||
|
||||
|
||||
|
||||
-- general-purpose helpers
|
||||
----------------------------
|
||||
|
||||
mModify :: MonadMultiState s m => (s -> s) -> m ()
|
||||
mModify f = mGet >>= mSet . f
|
||||
|
||||
-- sadly, you need a degree in type inference to know when we can use
|
||||
-- these operators and when it must be avoided due to type ambiguities
|
||||
-- arising around s in the signatures below. That's the price of not having
|
||||
-- the functional dependency in MonadMulti*T.
|
||||
|
||||
(.=+) :: MonadMultiState s m
|
||||
=> Lens.ASetter s s a b -> b -> m ()
|
||||
l .=+ b = mModify $ l .~ b
|
||||
|
||||
(%=+) :: MonadMultiState s m
|
||||
=> Lens.ASetter s s a b -> (a -> b) -> m ()
|
||||
l %=+ f = mModify (l %~ f)
|
||||
|
||||
-- inflateStateProxy :: (Monad m, ContainsType s ss)
|
||||
-- => p s -> StateS.StateT s m a -> MultiRWSS.MultiRWST r w ss m a
|
||||
-- inflateStateProxy _ = MultiRWSS.inflateState
|
||||
|
||||
-- actual CmdBuilder stuff
|
||||
----------------------------
|
||||
|
||||
instance IsHelpBuilder (CmdBuilder out) where
|
||||
help s = liftAp $ CmdBuilderHelp s ()
|
||||
|
||||
instance IsHelpBuilder (ParamBuilder p) where
|
||||
help s = liftAp $ ParamBuilderHelp s ()
|
||||
|
||||
instance IsHelpBuilder FlagBuilder where
|
||||
help s = liftAp $ FlagBuilderHelp s ()
|
||||
|
||||
addCmd :: String -> CmdBuilder out () -> CmdBuilder out ()
|
||||
addCmd s m = liftAp $ CmdBuilderChild s m ()
|
||||
|
||||
addParam :: (Show p, IsParam p) => String -> ParamBuilder p () -> CmdBuilder out p
|
||||
addParam s m = liftAp $ CmdBuilderParam s m id
|
||||
|
||||
{-# NOINLINE addFlag #-}
|
||||
addFlag :: String -> [String] -> FlagBuilder a -> CmdBuilder out [a]
|
||||
addFlag shorts longs m = Unsafe.performIO $ do
|
||||
unique <- Data.Unique.newUnique
|
||||
return $ liftAp $ CmdBuilderFlag unique shorts longs m id
|
||||
|
||||
impl :: a -> CmdBuilder a ()
|
||||
impl x = liftAp $ CmdBuilderRun x ()
|
||||
|
||||
def :: p -> ParamBuilder p ()
|
||||
def d = liftAp $ ParamBuilderDef d ()
|
||||
|
||||
-- | Does some limited "static" testing on a CmdBuilder.
|
||||
-- "static" as in: it does not read any actual input.
|
||||
-- Mostly checks that certain things are not defined multiple times,
|
||||
-- e.g. help annotations.
|
||||
cmdCheckNonStatic :: CmdBuilder out () -> Maybe String
|
||||
cmdCheckNonStatic cmdBuilder = join
|
||||
$ Data.Either.Combinators.leftToMaybe
|
||||
$ flip StateS.evalState emptyCommand
|
||||
$ runEitherT
|
||||
$ runAp iterFunc cmdBuilder
|
||||
where
|
||||
iterFunc :: CmdBuilderF out a
|
||||
-> EitherT (Maybe String)
|
||||
(StateS.State (Command out0)) a
|
||||
iterFunc = \case
|
||||
CmdBuilderHelp h r -> do
|
||||
cmd <- State.Class.get
|
||||
case _cmd_help cmd of
|
||||
Nothing ->
|
||||
cmd_help .= Just h
|
||||
Just{} ->
|
||||
left $ Just $ "help is already defined when trying to add help \"" ++ h ++ "\""
|
||||
pure r
|
||||
CmdBuilderFlag funique _shorts _longs f r -> do
|
||||
case checkFlag funique f of -- yes, this is a mapM_.
|
||||
Nothing -> pure () -- but that does not help readability.
|
||||
err -> left $ err
|
||||
pure $ r []
|
||||
CmdBuilderParam _ p r -> do
|
||||
case checkParam p of
|
||||
Nothing -> pure ()
|
||||
err -> left $ err
|
||||
pure $ r $ paramStaticDef
|
||||
CmdBuilderChild _s c r -> do
|
||||
case cmdCheckNonStatic c of
|
||||
Nothing -> pure ()
|
||||
err -> left $ err
|
||||
pure r
|
||||
CmdBuilderRun _o _r ->
|
||||
left Nothing
|
||||
checkFlag :: Unique -> FlagBuilder b -> Maybe String
|
||||
checkFlag unique flagBuilder = join
|
||||
$ Data.Either.Combinators.leftToMaybe
|
||||
$ flip StateS.evalState (Flag unique "" [] Nothing [])
|
||||
$ runEitherT
|
||||
$ runAp iterFuncFlag flagBuilder
|
||||
where
|
||||
iterFuncFlag :: FlagBuilderF b
|
||||
-> EitherT (Maybe String) (StateS.State Flag) b
|
||||
iterFuncFlag = \case
|
||||
FlagBuilderHelp h r -> do
|
||||
param <- State.Class.get
|
||||
case _flag_help param of
|
||||
Nothing ->
|
||||
flag_help .= Just h
|
||||
Just{} ->
|
||||
left $ Just $ "help is already defined when trying to add help \"" ++ h ++ "\""
|
||||
pure r
|
||||
FlagBuilderParam _s p r -> do
|
||||
case checkParam p of
|
||||
Nothing -> pure ()
|
||||
err -> left $ err
|
||||
pure $ r $ paramStaticDef
|
||||
checkParam :: Show p => ParamBuilder p () -> Maybe String
|
||||
checkParam paramBuilder = join
|
||||
$ Data.Either.Combinators.leftToMaybe
|
||||
$ flip StateS.evalState (Param Nothing Nothing)
|
||||
$ runEitherT
|
||||
$ runAp iterFuncParam paramBuilder
|
||||
where
|
||||
iterFuncParam :: Show p
|
||||
=> ParamBuilderF p a
|
||||
-> EitherT (Maybe String) (StateS.State (Param p)) a
|
||||
iterFuncParam = \case
|
||||
ParamBuilderHelp h r -> do
|
||||
param <- State.Class.get
|
||||
case _param_help param of
|
||||
Nothing ->
|
||||
param_help .= Just h
|
||||
Just{} ->
|
||||
left $ Just $ "help is already defined when trying to add help \"" ++ h ++ "\""
|
||||
pure $ r
|
||||
ParamBuilderDef d r -> do
|
||||
param <- State.Class.get
|
||||
case _param_def param of
|
||||
Nothing ->
|
||||
param_def .= Just d
|
||||
Just{} ->
|
||||
left $ Just $ "default is already defined when trying to add default \"" ++ show d ++ "\""
|
||||
pure $ r
|
||||
|
||||
cmdGetPartial :: forall out . String
|
||||
-> CmdBuilder out ()
|
||||
-> ( [String] -- errors
|
||||
, String -- remaining string
|
||||
, Command out -- current result, as far as parsing was possible.
|
||||
-- (!) take care not to run this command's action
|
||||
-- if there are errors (!)
|
||||
)
|
||||
cmdGetPartial inputStr cmdBuilder
|
||||
= runIdentity
|
||||
$ MultiRWSS.runMultiRWSTNil
|
||||
$ (<&> captureFinal)
|
||||
$ MultiRWSS.withMultiWriterWA
|
||||
$ MultiRWSS.withMultiStateSA inputStr
|
||||
$ MultiRWSS.withMultiStateS emptyCommand
|
||||
$ processMain cmdBuilder
|
||||
where
|
||||
-- make sure that all input is processed; otherwise
|
||||
-- add an error.
|
||||
-- Does not use the writer because this method does some tuple
|
||||
-- shuffling.
|
||||
captureFinal :: ([String], (String, Command out))
|
||||
-> ([String], String, Command out)
|
||||
captureFinal (errs, (s, cmd)) = (errs', s, cmd)
|
||||
where
|
||||
errs' = errs ++ if not $ all Char.isSpace s
|
||||
then ["could not parse input at " ++ s]
|
||||
else []
|
||||
|
||||
-- main "interpreter" over the free monad. not implemented as an iteration
|
||||
-- because we switch to a different interpreter (and interpret the same
|
||||
-- stuff more than once) when processing flags.
|
||||
processMain :: CmdBuilder out ()
|
||||
-> MultiRWSS.MultiRWS '[] '[[String]] '[Command out, String] ()
|
||||
processMain = \case
|
||||
Pure x -> return x
|
||||
Ap (CmdBuilderHelp h r) next -> do
|
||||
cmd :: Command out <- mGet
|
||||
mSet $ cmd { _cmd_help = Just h }
|
||||
processMain $ ($ r) <$> next
|
||||
f@(Ap (CmdBuilderFlag{}) _) -> do
|
||||
flagData <- MultiRWSS.withMultiWriterW $ -- WriterS.execWriterT $
|
||||
runAp iterFlagGather f
|
||||
do
|
||||
cmd :: Command out <- mGet
|
||||
mSet $ cmd { _cmd_flags = _cmd_flags cmd ++ flagData }
|
||||
parsedFlag <- MultiRWSS.withMultiStateS (Map.empty :: FlagParsedMap)
|
||||
$ parseFlags flagData
|
||||
(finalMap, fr) <- MultiRWSS.withMultiStateSA parsedFlag $ runParsedFlag f
|
||||
if Map.null finalMap
|
||||
then processMain fr
|
||||
else mTell ["internal error in application or colint library: inconsistent flag definitions."]
|
||||
Ap (CmdBuilderParam s p r) next -> do
|
||||
let param = processParam p
|
||||
cmd :: Command out <- mGet
|
||||
mSet $ cmd { _cmd_params = _cmd_params cmd ++ [ParamA s param] }
|
||||
str <- mGet
|
||||
x <- case (paramParse str, _param_def param) of
|
||||
(Nothing, Just x) -> do
|
||||
-- did not parse, use configured default value
|
||||
return $ x
|
||||
(Nothing, Nothing) -> do
|
||||
-- did not parse, no default value. add error, cont. with static default.
|
||||
mTell ["could not parse param at " ++ str]
|
||||
return paramStaticDef
|
||||
(Just (v, _, x), _) -> do
|
||||
-- parsed value; update the rest-string-to-parse, return value.
|
||||
mSet $ x
|
||||
return $ v
|
||||
processMain $ ($ r x) <$> next
|
||||
Ap (CmdBuilderChild s c r) next -> do
|
||||
dropSpaces
|
||||
str <- mGet
|
||||
let mRest = if
|
||||
| s == str -> Just ""
|
||||
| (s++" ") `isPrefixOf` str -> Just $ drop (length s + 1) str
|
||||
| otherwise -> Nothing
|
||||
case mRest of
|
||||
Nothing -> do
|
||||
cmd :: Command out <- mGet
|
||||
subCmd <- MultiRWSS.withMultiStateS emptyCommand
|
||||
$ runAp processCmdShallow c
|
||||
mSet $ cmd { _cmd_children = _cmd_children cmd ++ [(s, subCmd)] }
|
||||
processMain $ ($ r) <$> next
|
||||
Just rest -> do
|
||||
old :: Command out <- mGet
|
||||
mSet $ rest
|
||||
mSet $ emptyCommand {
|
||||
_cmd_mParent = Just (old, s)
|
||||
}
|
||||
processMain c
|
||||
Ap (CmdBuilderRun o r) next -> do
|
||||
cmd_run .=+ Just o
|
||||
processMain $ ($ r) <$> next
|
||||
|
||||
-- only captures some (i.e. roughly one layer) of the structure of
|
||||
-- the (remaining) builder, not parsing any input.
|
||||
processCmdShallow :: MonadMultiState (Command out) m
|
||||
=> CmdBuilderF out a
|
||||
-> m a
|
||||
processCmdShallow = \case
|
||||
CmdBuilderHelp h r -> do
|
||||
cmd :: Command out <- mGet
|
||||
mSet $ cmd {
|
||||
_cmd_help = Just h
|
||||
}
|
||||
pure $ r
|
||||
CmdBuilderFlag _funique _shorts _longs _f r -> do
|
||||
pure $ r []
|
||||
CmdBuilderParam s p r -> do
|
||||
cmd :: Command out <- mGet
|
||||
mSet $ cmd {
|
||||
_cmd_params = _cmd_params cmd ++ [ParamA s $ processParam p]
|
||||
}
|
||||
pure $ r $ paramStaticDef
|
||||
CmdBuilderChild s _c r -> do
|
||||
cmd_children %=+ (++[(s, emptyCommand :: Command out)])
|
||||
pure $ r
|
||||
CmdBuilderRun _o r ->
|
||||
pure $ r
|
||||
|
||||
-- extract a list of flag declarations. return [], i.e. pretend that no
|
||||
-- flag matches while doing so.
|
||||
iterFlagGather :: CmdBuilderF out a
|
||||
-> MultiRWSS.MultiRWS r ([Flag]':wr) s a
|
||||
iterFlagGather = \case
|
||||
-- x | trace ("iterFlagGather: " ++ show (x $> ())) False -> error "laksjdlkja"
|
||||
CmdBuilderFlag funique shorts longs f next -> do
|
||||
let flag = processFlag funique shorts longs f
|
||||
mTell $ [flag]
|
||||
pure $ next []
|
||||
CmdBuilderHelp _ r -> pure r
|
||||
CmdBuilderParam _ _ r -> pure $ r $ paramStaticDef
|
||||
CmdBuilderChild _ _ r -> pure r
|
||||
CmdBuilderRun _ r -> pure r
|
||||
|
||||
-- the second iteration (technically not an iterM, but close..) over flags:
|
||||
-- use the parsed flag map, so that the actual flag (values) are captured
|
||||
-- in this run.
|
||||
-- return the final CmdBuilder when a non-flag is encountered.
|
||||
runParsedFlag :: CmdBuilder out ()
|
||||
-> MultiRWSS.MultiRWS '[] '[[String]] '[FlagParsedMap, Command out, String] (CmdBuilder out ())
|
||||
runParsedFlag = \case
|
||||
Ap (CmdBuilderFlag funique _ _ f r) next -> do
|
||||
m :: FlagParsedMap <- mGet
|
||||
let flagRawStrs = case Map.lookup funique m of
|
||||
Nothing -> []
|
||||
Just x -> x
|
||||
mSet $ Map.delete funique m
|
||||
runParsedFlag $ next <&> \g -> g $ r $ reparseFlag f <$> flagRawStrs
|
||||
Pure x -> return $ pure x
|
||||
f -> return f
|
||||
|
||||
reparseFlag :: FlagBuilder b -> FlagParsedElement -> b
|
||||
reparseFlag = undefined -- TODO FIXME WHO LEFT THIS HERE
|
||||
|
||||
parseFlags :: ( MonadMultiWriter [String] m
|
||||
, MonadMultiState String m
|
||||
, MonadMultiState FlagParsedMap m
|
||||
)
|
||||
=> [Flag]
|
||||
-> m ()
|
||||
parseFlags flags = do
|
||||
dropSpaces
|
||||
str <- mGet
|
||||
case str of
|
||||
('-':'-':longRest) ->
|
||||
case getAlt $ mconcat $ flags <&> \f
|
||||
-> mconcat $ _flag_long f <&> \l
|
||||
-> let len = length l
|
||||
in Alt $ do
|
||||
guard $ isPrefixOf l longRest
|
||||
r <- case List.drop len longRest of
|
||||
"" -> return ""
|
||||
(' ':r) -> return r
|
||||
_ -> mzero
|
||||
return $ (l, r, f) of
|
||||
Nothing -> mTell ["could not understand flag at --" ++ longRest]
|
||||
Just (flagStr, flagRest, flag) ->
|
||||
if length (_flag_params flag) /= 0
|
||||
then error "flag params not supported yet!"
|
||||
else do
|
||||
mSet flagRest
|
||||
mModify $ Map.insertWith (++)
|
||||
(_flag_unique flag)
|
||||
[FlagParsedElement [flagStr]]
|
||||
('-':shortRest) ->
|
||||
case shortRest of
|
||||
(c:' ':r) ->
|
||||
case getAlt $ mconcat $ flags <&> \f
|
||||
-> mconcat $ _flag_short f <&> \s
|
||||
-> Alt $ do
|
||||
guard $ c==s
|
||||
r' <- case r of
|
||||
(' ':r') -> return r'
|
||||
_ -> mzero
|
||||
return (c, r', f) of
|
||||
Nothing -> mTell ["could not understand flag at -" ++ shortRest]
|
||||
Just (flagChr, flagRest, flag) ->
|
||||
if length (_flag_params flag) /= 0
|
||||
then error "flag params not supported yet!"
|
||||
else do
|
||||
mSet flagRest
|
||||
mModify $ Map.insertWith (++)
|
||||
(_flag_unique flag)
|
||||
[FlagParsedElement ["-"++[flagChr]]]
|
||||
_ -> mTell ["could not parse flag at -" ++ shortRest]
|
||||
_ -> pure ()
|
||||
dropSpaces :: MonadMultiState String m => m ()
|
||||
dropSpaces = mModify $ dropWhile Char.isSpace
|
||||
processFlag :: Unique -> [Char] -> [String] -> FlagBuilder b -> Flag
|
||||
processFlag unique shorts longs flagBuilder
|
||||
= flip StateS.execState (Flag unique shorts longs Nothing [])
|
||||
$ runAp iterFuncFlag flagBuilder
|
||||
where
|
||||
iterFuncFlag :: FlagBuilderF a
|
||||
-> (StateS.State Flag) a
|
||||
iterFuncFlag = \case
|
||||
FlagBuilderHelp h r -> (flag_help .= Just h) $> r
|
||||
FlagBuilderParam s p r -> do
|
||||
let param = processParam p
|
||||
flag_params %= (++ [ParamA s param])
|
||||
pure $ r $ paramStaticDef
|
||||
processParam :: Show p => ParamBuilder p () -> Param p
|
||||
processParam paramBuilder = flip StateS.execState emptyParam
|
||||
$ runEitherT
|
||||
$ runAp iterFuncParam paramBuilder
|
||||
where
|
||||
iterFuncParam :: Show p
|
||||
=> ParamBuilderF p a
|
||||
-> EitherT (Maybe String) (StateS.State (Param p)) a
|
||||
iterFuncParam = \case
|
||||
ParamBuilderHelp h r -> do
|
||||
param <- State.Class.get
|
||||
case _param_help param of
|
||||
Nothing ->
|
||||
param_help .= Just h
|
||||
Just{} ->
|
||||
left $ Just $ "help is already defined when trying to add help \"" ++ h ++ "\""
|
||||
pure $ r
|
||||
ParamBuilderDef d r -> do
|
||||
param <- State.Class.get
|
||||
case _param_def param of
|
||||
Nothing ->
|
||||
param_def .= Just d
|
||||
Just{} ->
|
||||
left $ Just $ "default is already defined when trying to add default \"" ++ show d ++ "\""
|
||||
pure $ r
|
||||
|
||||
cmdActionPartial :: Command out -> Either String out
|
||||
cmdActionPartial = maybe (Left err) Right . _cmd_run
|
||||
where
|
||||
err = "command is missing implementation!"
|
||||
|
||||
cmdAction :: String -> CmdBuilder out () -> Either String out
|
||||
cmdAction s b = case cmdGetPartial s b of
|
||||
([], _, cmd) -> cmdActionPartial cmd
|
||||
((out:_), _, _) -> Left $ out
|
||||
|
||||
ppCommand :: Command out -> String
|
||||
ppCommand cmd
|
||||
= PP.render
|
||||
$ PP.vcat
|
||||
[ case _cmd_help cmd of
|
||||
Nothing -> PP.empty
|
||||
Just x -> PP.text x
|
||||
, case _cmd_children cmd of
|
||||
[] -> PP.empty
|
||||
cs -> PP.text "commands:" PP.$$ PP.nest 2 (PP.vcat $ commandShort <$> cs)
|
||||
, case _cmd_flags cmd of
|
||||
[] -> PP.empty
|
||||
fs -> PP.text "flags:" PP.$$ PP.nest 2 (PP.vcat $ flagShort <$> fs)
|
||||
]
|
||||
where
|
||||
commandShort :: (String, Command out) -> PP.Doc
|
||||
commandShort (s, c)
|
||||
= PP.text (s ++ ((_cmd_params c) >>= \(ParamA ps _) -> " " ++ ps))
|
||||
PP.<> case _cmd_help c of
|
||||
Nothing -> PP.empty
|
||||
Just h -> PP.text ":" PP.<+> PP.text h
|
||||
flagShort :: Flag -> PP.Doc
|
||||
flagShort f = PP.hsep (PP.text . ("-"++) . return <$> _flag_short f)
|
||||
PP.<+> PP.hsep (PP.text . ("--"++) <$> _flag_long f)
|
||||
PP.<+> case _flag_help f of
|
||||
Nothing -> PP.empty
|
||||
Just h -> PP.text h
|
||||
|
||||
ppCommandShort :: Command out -> String
|
||||
ppCommandShort cmd
|
||||
= PP.render
|
||||
$ printParent cmd
|
||||
PP.<+>
|
||||
case _cmd_flags cmd of
|
||||
[] -> PP.empty
|
||||
fs -> tooLongText 20 "[FLAGS]" $ List.unwords $ fs <&> \f ->
|
||||
"["
|
||||
++ (List.unwords $ (_flag_short f <&> \c -> ['-', c])
|
||||
++ (_flag_long f <&> \l -> "--" ++ l)
|
||||
)
|
||||
++ "]"
|
||||
PP.<+>
|
||||
case _cmd_params cmd of
|
||||
[] -> PP.empty
|
||||
ps -> PP.text $ List.unwords $ ps <&> \(ParamA s _) -> Char.toUpper <$> s
|
||||
PP.<+>
|
||||
case _cmd_children cmd of
|
||||
[] -> PP.empty
|
||||
cs -> PP.text
|
||||
$ if Maybe.isJust $ _cmd_run cmd
|
||||
then "[<" ++ intercalate "|" (fst <$> cs) ++ ">]"
|
||||
else "<" ++ intercalate "|" (fst <$> cs) ++ ">"
|
||||
where
|
||||
printParent :: Command out -> PP.Doc
|
||||
printParent c = case _cmd_mParent c of
|
||||
Nothing -> PP.empty
|
||||
Just (p, x) -> printParent p PP.<+> PP.text x
|
||||
|
||||
ppCommandShortHelp :: Command out -> String
|
||||
ppCommandShortHelp cmd
|
||||
= PP.render
|
||||
$ printParent cmd
|
||||
PP.<+>
|
||||
case _cmd_flags cmd of
|
||||
[] -> PP.empty
|
||||
fs -> tooLongText 20 "[FLAGS]" $ List.unwords $ fs <&> \f ->
|
||||
"["
|
||||
++ (List.unwords $ (_flag_short f <&> \c -> ['-', c])
|
||||
++ (_flag_long f <&> \l -> "--" ++ l)
|
||||
)
|
||||
++ "]"
|
||||
PP.<+>
|
||||
case _cmd_params cmd of
|
||||
[] -> PP.empty
|
||||
ps -> PP.text $ List.unwords $ ps <&> \(ParamA s _) -> Char.toUpper <$> s
|
||||
PP.<+>
|
||||
case _cmd_children cmd of
|
||||
[] -> PP.empty
|
||||
cs -> PP.text
|
||||
$ if Maybe.isJust $ _cmd_run cmd
|
||||
then "[<" ++ intercalate "|" (fst <$> cs) ++ ">]"
|
||||
else "<" ++ intercalate "|" (fst <$> cs) ++ ">"
|
||||
PP.<>
|
||||
case _cmd_help cmd of
|
||||
Nothing -> PP.empty
|
||||
Just h -> PP.text ":" PP.<+> PP.text h
|
||||
where
|
||||
printParent :: Command out -> PP.Doc
|
||||
printParent c = case _cmd_mParent c of
|
||||
Nothing -> PP.empty
|
||||
Just (p, x) -> printParent p PP.<+> PP.text x
|
||||
|
||||
tooLongText :: Int -- max length
|
||||
-> String -- alternative if actual length is bigger than max.
|
||||
-> String -- text to print, if length is fine.
|
||||
-> PP.Doc
|
||||
tooLongText i alt s = PP.text $ Bool.bool alt s $ null $ drop i s
|
||||
|
||||
-- TODO
|
||||
{-
|
||||
cmds :: CmdBuilder (IO ()) ()
|
||||
cmds = do
|
||||
_ <- addCmd "echo" $ do
|
||||
_ <- help "print its parameter to output"
|
||||
str <- addParam "string" $ do
|
||||
_ <- help "the string to print"
|
||||
pure ()
|
||||
-- def "foo"
|
||||
_ <- impl $ do
|
||||
putStrLn str
|
||||
pure ()
|
||||
addCmd "hello" $ do
|
||||
help "prints some greeting"
|
||||
short <- flagAsBool $ addFlag "" ["short"] $ pure ()
|
||||
name <- addParam "name" $ do
|
||||
_ <- help "your name, so you can be greeted properly"
|
||||
_ <- def "user"
|
||||
pure ()
|
||||
impl $ do
|
||||
if short
|
||||
then putStrLn $ "hi, " ++ name ++"!"
|
||||
else putStrLn $ "hello, " ++ name ++", welcome to colint!"
|
||||
pure ()
|
||||
pure ()
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
case cmdCheckNonStatic cmds of
|
||||
Just err -> do
|
||||
putStrLn "error building commands!!"
|
||||
putStrLn err
|
||||
Nothing -> do
|
||||
forever $ do
|
||||
putStr "> "
|
||||
hFlush stdout
|
||||
input <- System.IO.getLine
|
||||
let (errs, _, partial) = cmdGetPartial input cmds
|
||||
print partial
|
||||
putStrLn $ ppCommand $ partial
|
||||
case (errs, cmdActionPartial partial) of
|
||||
(err:_, _) -> print err
|
||||
([], eEff) -> case eEff of
|
||||
Left err -> do
|
||||
putStrLn $ "could not interpret input: " ++ err
|
||||
Right eff -> do
|
||||
eff
|
||||
-}
|
||||
|
||||
flagAsBool :: CmdBuilder m [a] -> CmdBuilder m Bool
|
||||
flagAsBool = fmap (not . null)
|
||||
|
||||
-- ----
|
||||
|
||||
instance IsParam String where
|
||||
paramParse s = do
|
||||
let s1 = dropWhile Char.isSpace s
|
||||
let (param, rest) = List.span (not . Char.isSpace) s1
|
||||
guard $ not $ null param
|
||||
pure $ (param, param, rest) -- we remove trailing whitespace, evil as we are.
|
||||
paramStaticDef = ""
|
||||
|
||||
instance IsParam () where
|
||||
paramParse s = do
|
||||
let s1 = dropWhile Char.isSpace s
|
||||
rest <- List.stripPrefix "()" s1
|
||||
pure $ ((), "()", rest)
|
||||
paramStaticDef = ()
|
|
@ -0,0 +1,155 @@
|
|||
{-# LANGUAGE DeriveFunctor #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE ExistentialQuantification #-}
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
{-# LANGUAGE StandaloneDeriving #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
{-# LANGUAGE MonadComprehensions #-}
|
||||
|
||||
module UI.CmdParse.Applicative.Types
|
||||
( Command(..)
|
||||
, cmd_mParent
|
||||
, cmd_help
|
||||
, cmd_flags
|
||||
, cmd_params
|
||||
, cmd_children
|
||||
, cmd_run
|
||||
, flag_help
|
||||
, flag_long
|
||||
, flag_params
|
||||
, flag_unique
|
||||
, flag_short
|
||||
, param_def
|
||||
, param_help
|
||||
, emptyCommand
|
||||
, emptyParam
|
||||
, FlagParsedMap
|
||||
, FlagParsedElement(..)
|
||||
, IsParam(..)
|
||||
, IsHelpBuilder(..)
|
||||
, CmdBuilderF(..)
|
||||
, CmdBuilder
|
||||
, ParamBuilderF(..)
|
||||
, ParamBuilder
|
||||
, FlagBuilderF(..)
|
||||
, FlagBuilder
|
||||
, Flag(..)
|
||||
, Param(..)
|
||||
, ParamA(..)
|
||||
)
|
||||
where
|
||||
|
||||
|
||||
|
||||
#include "qprelude/bundle-gamma.inc"
|
||||
import Control.Applicative.Free
|
||||
import qualified Control.Monad.Trans.MultiState.Strict as MultiStateS
|
||||
import Data.Unique (Unique)
|
||||
import qualified System.Unsafe as Unsafe
|
||||
|
||||
import qualified Control.Lens.TH as LensTH
|
||||
import qualified Control.Lens as Lens
|
||||
|
||||
|
||||
|
||||
data Command out = Command
|
||||
{ _cmd_mParent :: Maybe (Command out, String) -- parent command
|
||||
-- , substring that leads to $this.
|
||||
-- (kinda wonky, i know.)
|
||||
, _cmd_help :: Maybe String
|
||||
, _cmd_flags :: [Flag]
|
||||
, _cmd_params :: [ParamA]
|
||||
, _cmd_children :: [(String, Command out)]
|
||||
, _cmd_run :: Maybe out
|
||||
}
|
||||
|
||||
emptyCommand :: Command out
|
||||
emptyCommand = Command Nothing Nothing [] [] [] Nothing
|
||||
|
||||
instance Show (Command out) where
|
||||
show c = "Command help=" ++ show (_cmd_help c)
|
||||
++ " flags=" ++ show (_cmd_flags c)
|
||||
++ " params=" ++ show (_cmd_params c)
|
||||
++ " children=" ++ show (_cmd_children c)
|
||||
++ " run=" ++ case _cmd_run c of Nothing -> "Nothing"; Just{} -> "Just{..}"
|
||||
|
||||
-- class IsFlag a where
|
||||
-- flagParse :: String -> Maybe (a, String)
|
||||
-- staticDef :: a
|
||||
|
||||
data Flag = Flag
|
||||
{ _flag_unique :: Unique
|
||||
, _flag_short :: String
|
||||
, _flag_long :: [String]
|
||||
, _flag_help :: Maybe String
|
||||
, _flag_params :: [ParamA]
|
||||
}
|
||||
|
||||
instance Show Flag where
|
||||
show (Flag _ short long helpM params) = show (short, long, helpM, params) -- TODO: improve
|
||||
|
||||
type FlagParsedMap = Map Unique [FlagParsedElement]
|
||||
|
||||
data FlagParsedElement = FlagParsedElement [String]
|
||||
deriving Show
|
||||
|
||||
data ParamA = forall p . (IsParam p, Show p) => ParamA String (Param p)
|
||||
|
||||
deriving instance Show ParamA
|
||||
|
||||
class IsParam a where
|
||||
paramParse :: String -> Maybe (a, String, String) -- value, representation, rest
|
||||
paramStaticDef :: a
|
||||
|
||||
data Param a = Param
|
||||
{ _param_help :: Maybe String
|
||||
, _param_def :: Maybe a
|
||||
}
|
||||
|
||||
emptyParam :: Param a
|
||||
emptyParam = Param Nothing Nothing
|
||||
|
||||
deriving instance Show a => Show (Param a)
|
||||
|
||||
data CmdBuilderF out a
|
||||
= CmdBuilderHelp String a
|
||||
| forall b . CmdBuilderFlag Unique String [String] (FlagBuilder b) ([b] -> a)
|
||||
| forall p . (Show p, IsParam p) => CmdBuilderParam String (ParamBuilder p ()) (p -> a)
|
||||
| CmdBuilderChild String (CmdBuilder out ()) a
|
||||
| CmdBuilderRun out a
|
||||
|
||||
deriving instance Functor (CmdBuilderF out)
|
||||
|
||||
instance Show a => Show (CmdBuilderF out a) where
|
||||
show (CmdBuilderHelp s x) = "(CmdBuilderHelp " ++ show s ++ " " ++ show x ++ ")"
|
||||
show (CmdBuilderFlag _ shorts longs _ _) = "(CmdBuilderFlag -" ++ shorts ++ " " ++ show longs ++ ")"
|
||||
show (CmdBuilderParam s _ _) = "(CmdBuilderParam " ++ s ++ ")"
|
||||
show (CmdBuilderChild s _ _) = "(CmdBuilderChild " ++ s ++ ")"
|
||||
show (CmdBuilderRun _ _) = "CmdBuilderRun"
|
||||
|
||||
type CmdBuilder out = Ap (CmdBuilderF out)
|
||||
|
||||
data FlagBuilderF a
|
||||
= FlagBuilderHelp String a
|
||||
| forall p . (Show p, IsParam p) => FlagBuilderParam String (ParamBuilder p ()) (p -> a)
|
||||
|
||||
deriving instance Functor FlagBuilderF
|
||||
|
||||
type FlagBuilder = Ap FlagBuilderF
|
||||
|
||||
data ParamBuilderF p a
|
||||
= ParamBuilderHelp String a
|
||||
| ParamBuilderDef p a
|
||||
|
||||
deriving instance Functor (ParamBuilderF p)
|
||||
|
||||
type ParamBuilder p = Ap (ParamBuilderF p)
|
||||
|
||||
class IsHelpBuilder m where
|
||||
help :: String -> m ()
|
||||
|
||||
Lens.makeLenses ''Command
|
||||
Lens.makeLenses ''Flag
|
||||
Lens.makeLenses ''Param
|
|
@ -0,0 +1,616 @@
|
|||
{-# LANGUAGE DeriveFunctor #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE ExistentialQuantification #-}
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
{-# LANGUAGE StandaloneDeriving #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE TypeOperators #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
{-# LANGUAGE MonadComprehensions #-}
|
||||
{-# LANGUAGE MultiWayIf #-}
|
||||
|
||||
module UI.CmdParse.Monadic
|
||||
( main
|
||||
, cmdActionPartial
|
||||
, cmdAction
|
||||
, cmd_run
|
||||
, CmdBuilder
|
||||
, addFlag
|
||||
, addCmd
|
||||
, addParam
|
||||
, help
|
||||
, impl
|
||||
, def
|
||||
, flagAsBool
|
||||
, cmdGetPartial
|
||||
, ppCommand
|
||||
, ppCommandShort
|
||||
, ppCommandShortHelp
|
||||
-- re-exports:
|
||||
, Command(..)
|
||||
, Flag(..)
|
||||
, Param(..)
|
||||
)
|
||||
where
|
||||
|
||||
|
||||
|
||||
#include "qprelude/bundle-gamma.inc"
|
||||
import Control.Monad.Free
|
||||
import qualified Control.Monad.Trans.MultiRWS.Strict as MultiRWSS
|
||||
import qualified Control.Monad.Trans.MultiState.Strict as MultiStateS
|
||||
import Data.Unique (Unique)
|
||||
import qualified System.Unsafe as Unsafe
|
||||
|
||||
import qualified Control.Lens.TH as LensTH
|
||||
import qualified Control.Lens as Lens
|
||||
import Control.Lens ( (.=), (%=), (%~), (.~) )
|
||||
|
||||
import qualified Text.PrettyPrint as PP
|
||||
|
||||
import UI.CmdParse.Monadic.Types
|
||||
|
||||
import Data.HList.ContainsType
|
||||
|
||||
|
||||
|
||||
-- general-purpose helpers
|
||||
----------------------------
|
||||
|
||||
mModify :: MonadMultiState s m => (s -> s) -> m ()
|
||||
mModify f = mGet >>= mSet . f
|
||||
|
||||
-- sadly, you need a degree in type inference to know when we can use
|
||||
-- these operators and when it must be avoided due to type ambiguities
|
||||
-- arising around s in the signatures below. That's the price of not having
|
||||
-- the functional dependency in MonadMulti*T.
|
||||
|
||||
(.=+) :: MonadMultiState s m
|
||||
=> Lens.ASetter s s a b -> b -> m ()
|
||||
l .=+ b = mModify $ l .~ b
|
||||
|
||||
(%=+) :: MonadMultiState s m
|
||||
=> Lens.ASetter s s a b -> (a -> b) -> m ()
|
||||
l %=+ f = mModify (l %~ f)
|
||||
|
||||
-- inflateStateProxy :: (Monad m, ContainsType s ss)
|
||||
-- => p s -> StateS.StateT s m a -> MultiRWSS.MultiRWST r w ss m a
|
||||
-- inflateStateProxy _ = MultiRWSS.inflateState
|
||||
|
||||
-- actual CmdBuilder stuff
|
||||
----------------------------
|
||||
|
||||
instance IsHelpBuilder (CmdBuilder out) where
|
||||
help s = liftF $ CmdBuilderHelp s ()
|
||||
|
||||
instance IsHelpBuilder (ParamBuilder p) where
|
||||
help s = liftF $ ParamBuilderHelp s ()
|
||||
|
||||
instance IsHelpBuilder FlagBuilder where
|
||||
help s = liftF $ FlagBuilderHelp s ()
|
||||
|
||||
addCmd :: String -> CmdBuilder out () -> CmdBuilder out ()
|
||||
addCmd s m = liftF $ CmdBuilderChild s m ()
|
||||
|
||||
addParam :: (Show p, IsParam p) => String -> ParamBuilder p () -> CmdBuilder out p
|
||||
addParam s m = liftF $ CmdBuilderParam s m id
|
||||
|
||||
{-# NOINLINE addFlag #-}
|
||||
addFlag :: String -> [String] -> FlagBuilder a -> CmdBuilder out [a]
|
||||
addFlag shorts longs m = Unsafe.performIO $ do
|
||||
unique <- Data.Unique.newUnique
|
||||
return $ liftF $ CmdBuilderFlag unique shorts longs m id
|
||||
|
||||
impl :: a -> CmdBuilder a ()
|
||||
impl x = liftF $ CmdBuilderRun x
|
||||
|
||||
def :: p -> ParamBuilder p ()
|
||||
def d = liftF $ ParamBuilderDef d ()
|
||||
|
||||
-- | Does some limited "static" testing on a CmdBuilder.
|
||||
-- "static" as in: it does not read any actual input.
|
||||
-- Mostly checks that certain things are not defined multiple times,
|
||||
-- e.g. help annotations.
|
||||
cmdCheckNonStatic :: CmdBuilder out () -> Maybe String
|
||||
cmdCheckNonStatic cmdBuilder = join
|
||||
$ Data.Either.Combinators.leftToMaybe
|
||||
$ flip StateS.evalState emptyCommand
|
||||
$ runEitherT
|
||||
$ iterM iterFunc cmdBuilder
|
||||
where
|
||||
iterFunc :: CmdBuilderF out
|
||||
(EitherT (Maybe String)
|
||||
(StateS.State (Command out0)) a)
|
||||
-> EitherT (Maybe String)
|
||||
(StateS.State (Command out0)) a
|
||||
iterFunc = \case
|
||||
CmdBuilderHelp h next -> do
|
||||
cmd <- State.Class.get
|
||||
case _cmd_help cmd of
|
||||
Nothing ->
|
||||
cmd_help .= Just h
|
||||
Just{} ->
|
||||
left $ Just $ "help is already defined when trying to add help \"" ++ h ++ "\""
|
||||
next
|
||||
CmdBuilderFlag funique _shorts _longs f next -> do
|
||||
case checkFlag funique f of -- yes, this is a mapM_.
|
||||
Nothing -> pure () -- but that does not help readability.
|
||||
err -> left $ err
|
||||
next []
|
||||
CmdBuilderParam _ p next -> do
|
||||
case checkParam p of
|
||||
Nothing -> pure ()
|
||||
err -> left $ err
|
||||
next $ paramStaticDef
|
||||
CmdBuilderChild _s c next -> do
|
||||
case cmdCheckNonStatic c of
|
||||
Nothing -> pure ()
|
||||
err -> left $ err
|
||||
next
|
||||
CmdBuilderRun _o ->
|
||||
left Nothing
|
||||
checkFlag :: Unique -> FlagBuilder b -> Maybe String
|
||||
checkFlag unique flagBuilder = join
|
||||
$ Data.Either.Combinators.leftToMaybe
|
||||
$ flip StateS.evalState (Flag unique "" [] Nothing [])
|
||||
$ runEitherT
|
||||
$ iterM iterFuncFlag flagBuilder
|
||||
where
|
||||
iterFuncFlag :: FlagBuilderF (EitherT (Maybe String) (StateS.State Flag) b)
|
||||
-> EitherT (Maybe String) (StateS.State Flag) b
|
||||
iterFuncFlag = \case
|
||||
FlagBuilderHelp h next -> do
|
||||
param <- State.Class.get
|
||||
case _flag_help param of
|
||||
Nothing ->
|
||||
flag_help .= Just h
|
||||
Just{} ->
|
||||
left $ Just $ "help is already defined when trying to add help \"" ++ h ++ "\""
|
||||
next
|
||||
FlagBuilderParam _s p next -> do
|
||||
case checkParam p of
|
||||
Nothing -> pure ()
|
||||
err -> left $ err
|
||||
next $ paramStaticDef
|
||||
checkParam :: Show p => ParamBuilder p () -> Maybe String
|
||||
checkParam paramBuilder = join
|
||||
$ Data.Either.Combinators.leftToMaybe
|
||||
$ flip StateS.evalState (Param Nothing Nothing)
|
||||
$ runEitherT
|
||||
$ iterM iterFuncParam paramBuilder
|
||||
where
|
||||
iterFuncParam :: Show p
|
||||
=> ParamBuilderF p (EitherT (Maybe String) (StateS.State (Param p)) ())
|
||||
-> EitherT (Maybe String) (StateS.State (Param p)) ()
|
||||
iterFuncParam = \case
|
||||
ParamBuilderHelp h next -> do
|
||||
param <- State.Class.get
|
||||
case _param_help param of
|
||||
Nothing ->
|
||||
param_help .= Just h
|
||||
Just{} ->
|
||||
left $ Just $ "help is already defined when trying to add help \"" ++ h ++ "\""
|
||||
next
|
||||
ParamBuilderDef d next -> do
|
||||
param <- State.Class.get
|
||||
case _param_def param of
|
||||
Nothing ->
|
||||
param_def .= Just d
|
||||
Just{} ->
|
||||
left $ Just $ "default is already defined when trying to add default \"" ++ show d ++ "\""
|
||||
next
|
||||
|
||||
cmdGetPartial :: forall out . String
|
||||
-> CmdBuilder out ()
|
||||
-> ( [String] -- errors
|
||||
, String -- remaining string
|
||||
, Command out -- current result, as far as parsing was possible.
|
||||
-- (!) take care not to run this command's action
|
||||
-- if there are errors (!)
|
||||
)
|
||||
cmdGetPartial inputStr cmdBuilder
|
||||
= runIdentity
|
||||
$ MultiRWSS.runMultiRWSTNil
|
||||
$ (<&> captureFinal)
|
||||
$ MultiRWSS.withMultiWriterWA
|
||||
$ MultiRWSS.withMultiStateSA inputStr
|
||||
$ MultiRWSS.withMultiStateS emptyCommand
|
||||
$ processMain cmdBuilder
|
||||
where
|
||||
-- make sure that all input is processed; otherwise
|
||||
-- add an error.
|
||||
-- Does not use the writer because this method does some tuple
|
||||
-- shuffling.
|
||||
captureFinal :: ([String], (String, Command out))
|
||||
-> ([String], String, Command out)
|
||||
captureFinal (errs, (s, cmd)) = (errs', s, cmd)
|
||||
where
|
||||
errs' = errs ++ if not $ all Char.isSpace s
|
||||
then ["could not parse input at " ++ s]
|
||||
else []
|
||||
|
||||
-- main "interpreter" over the free monad. not implemented as an iteration
|
||||
-- because we switch to a different interpreter (and interpret the same
|
||||
-- stuff more than once) when processing flags.
|
||||
processMain :: CmdBuilder out ()
|
||||
-> MultiRWSS.MultiRWS '[] '[[String]] '[Command out, String] ()
|
||||
processMain = \case
|
||||
Pure x -> return x
|
||||
Free (CmdBuilderHelp h next) -> do
|
||||
cmd :: Command out <- mGet
|
||||
mSet $ cmd { _cmd_help = Just h }
|
||||
processMain next
|
||||
f@(Free (CmdBuilderFlag{})) -> do
|
||||
flagData <- MultiRWSS.withMultiWriterW $ -- WriterS.execWriterT $
|
||||
iterM iterFlagGather f
|
||||
do
|
||||
cmd :: Command out <- mGet
|
||||
mSet $ cmd { _cmd_flags = _cmd_flags cmd ++ flagData }
|
||||
parsedFlag <- MultiRWSS.withMultiStateS (Map.empty :: FlagParsedMap)
|
||||
$ parseFlags flagData
|
||||
(finalMap, fr) <- MultiRWSS.withMultiStateSA parsedFlag $ runParsedFlag f
|
||||
if Map.null finalMap
|
||||
then processMain fr
|
||||
else mTell ["internal error in application or colint library: inconsistent flag definitions."]
|
||||
Free (CmdBuilderParam s p next) -> do
|
||||
let param = processParam p
|
||||
cmd :: Command out <- mGet
|
||||
mSet $ cmd { _cmd_params = _cmd_params cmd ++ [ParamA s param] }
|
||||
str <- mGet
|
||||
x <- case (paramParse str, _param_def param) of
|
||||
(Nothing, Just x) -> do
|
||||
-- did not parse, use configured default value
|
||||
return $ x
|
||||
(Nothing, Nothing) -> do
|
||||
-- did not parse, no default value. add error, cont. with static default.
|
||||
mTell ["could not parse param at " ++ str]
|
||||
return paramStaticDef
|
||||
(Just (v, _, r), _) -> do
|
||||
-- parsed value; update the rest-string-to-parse, return value.
|
||||
mSet $ r
|
||||
return $ v
|
||||
processMain $ next $ x
|
||||
Free (CmdBuilderChild s c next) -> do
|
||||
dropSpaces
|
||||
str <- mGet
|
||||
let mRest = if
|
||||
| s == str -> Just ""
|
||||
| (s++" ") `isPrefixOf` str -> Just $ drop (length s + 1) str
|
||||
| otherwise -> Nothing
|
||||
case mRest of
|
||||
Nothing -> do
|
||||
cmd :: Command out <- mGet
|
||||
subCmd <- MultiRWSS.withMultiStateS emptyCommand
|
||||
$ iterM processCmdShallow c
|
||||
mSet $ cmd { _cmd_children = _cmd_children cmd ++ [(s, subCmd)] }
|
||||
processMain next
|
||||
Just rest -> do
|
||||
old :: Command out <- mGet
|
||||
mSet $ rest
|
||||
mSet $ emptyCommand {
|
||||
_cmd_mParent = Just (old, s)
|
||||
}
|
||||
processMain c
|
||||
Free (CmdBuilderRun o) -> cmd_run .=+ Just o
|
||||
|
||||
-- only captures some (i.e. roughly one layer) of the structure of
|
||||
-- the (remaining) builder, not parsing any input.
|
||||
processCmdShallow :: MonadMultiState (Command out) m
|
||||
=> CmdBuilderF out (m ())
|
||||
-> m ()
|
||||
processCmdShallow = \case
|
||||
CmdBuilderHelp h next -> do
|
||||
cmd :: Command out <- mGet
|
||||
mSet $ cmd {
|
||||
_cmd_help = Just h
|
||||
}
|
||||
next
|
||||
CmdBuilderFlag _funique _shorts _longs _f next -> do
|
||||
next []
|
||||
CmdBuilderParam s p next -> do
|
||||
cmd :: Command out <- mGet
|
||||
mSet $ cmd {
|
||||
_cmd_params = _cmd_params cmd ++ [ParamA s $ processParam p]
|
||||
}
|
||||
next $ paramStaticDef
|
||||
CmdBuilderChild s _c next -> do
|
||||
cmd_children %=+ (++[(s, emptyCommand :: Command out)])
|
||||
next
|
||||
CmdBuilderRun _o ->
|
||||
return ()
|
||||
|
||||
-- extract a list of flag declarations. return [], i.e. pretend that no
|
||||
-- flag matches while doing so.
|
||||
iterFlagGather :: CmdBuilderF out (MultiRWSS.MultiRWS r ([Flag]':wr) s ())
|
||||
-> MultiRWSS.MultiRWS r ([Flag]':wr) s ()
|
||||
iterFlagGather = \case
|
||||
-- x | trace ("iterFlagGather: " ++ show (x $> ())) False -> error "laksjdlkja"
|
||||
CmdBuilderFlag funique shorts longs f next -> do
|
||||
let flag = processFlag funique shorts longs f
|
||||
mTell $ [flag]
|
||||
next []
|
||||
_ -> pure ()
|
||||
|
||||
-- the second iteration (technically not an iterM, but close..) over flags:
|
||||
-- use the parsed flag map, so that the actual flag (values) are captured
|
||||
-- in this run.
|
||||
-- return the final CmdBuilder when a non-flag is encountered.
|
||||
runParsedFlag :: CmdBuilder out ()
|
||||
-> MultiRWSS.MultiRWS '[] '[[String]] '[FlagParsedMap, Command out, String] (CmdBuilder out ())
|
||||
runParsedFlag = \case
|
||||
Free (CmdBuilderFlag funique _ _ f next) -> do
|
||||
m :: FlagParsedMap <- mGet
|
||||
let flagRawStrs = case Map.lookup funique m of
|
||||
Nothing -> []
|
||||
Just r -> r
|
||||
mSet $ Map.delete funique m
|
||||
runParsedFlag $ next $ reparseFlag f <$> flagRawStrs
|
||||
Pure x -> return $ return x
|
||||
f -> return f
|
||||
|
||||
reparseFlag :: FlagBuilder b -> FlagParsedElement -> b
|
||||
reparseFlag = undefined -- TODO FIXME WHO LEFT THIS HERE
|
||||
|
||||
parseFlags :: ( MonadMultiWriter [String] m
|
||||
, MonadMultiState String m
|
||||
, MonadMultiState FlagParsedMap m
|
||||
)
|
||||
=> [Flag]
|
||||
-> m ()
|
||||
parseFlags flags = do
|
||||
dropSpaces
|
||||
str <- mGet
|
||||
case str of
|
||||
('-':'-':longRest) ->
|
||||
case getAlt $ mconcat $ flags <&> \f
|
||||
-> mconcat $ _flag_long f <&> \l
|
||||
-> let len = length l
|
||||
in Alt $ do
|
||||
guard $ isPrefixOf l longRest
|
||||
r <- case List.drop len longRest of
|
||||
"" -> return ""
|
||||
(' ':r) -> return r
|
||||
_ -> mzero
|
||||
return $ (l, r, f) of
|
||||
Nothing -> mTell ["could not understand flag at --" ++ longRest]
|
||||
Just (flagStr, flagRest, flag) ->
|
||||
if length (_flag_params flag) /= 0
|
||||
then error "flag params not supported yet!"
|
||||
else do
|
||||
mSet flagRest
|
||||
mModify $ Map.insertWith (++)
|
||||
(_flag_unique flag)
|
||||
[FlagParsedElement [flagStr]]
|
||||
('-':shortRest) ->
|
||||
case shortRest of
|
||||
(c:' ':r) ->
|
||||
case getAlt $ mconcat $ flags <&> \f
|
||||
-> mconcat $ _flag_short f <&> \s
|
||||
-> Alt $ do
|
||||
guard $ c==s
|
||||
r' <- case r of
|
||||
(' ':r') -> return r'
|
||||
_ -> mzero
|
||||
return (c, r', f) of
|
||||
Nothing -> mTell ["could not understand flag at -" ++ shortRest]
|
||||
Just (flagChr, flagRest, flag) ->
|
||||
if length (_flag_params flag) /= 0
|
||||
then error "flag params not supported yet!"
|
||||
else do
|
||||
mSet flagRest
|
||||
mModify $ Map.insertWith (++)
|
||||
(_flag_unique flag)
|
||||
[FlagParsedElement ["-"++[flagChr]]]
|
||||
_ -> mTell ["could not parse flag at -" ++ shortRest]
|
||||
_ -> pure ()
|
||||
dropSpaces :: MonadMultiState String m => m ()
|
||||
dropSpaces = mModify $ dropWhile Char.isSpace
|
||||
processFlag :: Unique -> [Char] -> [String] -> FlagBuilder b -> Flag
|
||||
processFlag unique shorts longs flagBuilder
|
||||
= flip StateS.execState (Flag unique shorts longs Nothing [])
|
||||
$ iterM iterFuncFlag flagBuilder
|
||||
where
|
||||
iterFuncFlag :: FlagBuilderF ((StateS.State Flag) a)
|
||||
-> (StateS.State Flag) a
|
||||
iterFuncFlag = \case
|
||||
FlagBuilderHelp h next -> flag_help .= Just h >> next
|
||||
FlagBuilderParam s p next -> do
|
||||
let param = processParam p
|
||||
flag_params %= (++ [ParamA s param])
|
||||
next $ paramStaticDef
|
||||
processParam :: Show p => ParamBuilder p () -> Param p
|
||||
processParam paramBuilder = flip StateS.execState emptyParam
|
||||
$ runEitherT
|
||||
$ iterM iterFuncParam paramBuilder
|
||||
where
|
||||
iterFuncParam :: Show p
|
||||
=> ParamBuilderF p (EitherT (Maybe String) (StateS.State (Param p)) ())
|
||||
-> EitherT (Maybe String) (StateS.State (Param p)) ()
|
||||
iterFuncParam = \case
|
||||
ParamBuilderHelp h next -> do
|
||||
param <- State.Class.get
|
||||
case _param_help param of
|
||||
Nothing ->
|
||||
param_help .= Just h
|
||||
Just{} ->
|
||||
left $ Just $ "help is already defined when trying to add help \"" ++ h ++ "\""
|
||||
next
|
||||
ParamBuilderDef d next -> do
|
||||
param <- State.Class.get
|
||||
case _param_def param of
|
||||
Nothing ->
|
||||
param_def .= Just d
|
||||
Just{} ->
|
||||
left $ Just $ "default is already defined when trying to add default \"" ++ show d ++ "\""
|
||||
next
|
||||
|
||||
cmdActionPartial :: Command out -> Either String out
|
||||
cmdActionPartial = maybe (Left err) Right . _cmd_run
|
||||
where
|
||||
err = "command is missing implementation!"
|
||||
|
||||
cmdAction :: String -> CmdBuilder out () -> Either String out
|
||||
cmdAction s b = case cmdGetPartial s b of
|
||||
([], _, cmd) -> cmdActionPartial cmd
|
||||
((out:_), _, _) -> Left $ out
|
||||
|
||||
ppCommand :: Command out -> String
|
||||
ppCommand cmd
|
||||
= PP.render
|
||||
$ PP.vcat
|
||||
[ case _cmd_help cmd of
|
||||
Nothing -> PP.empty
|
||||
Just x -> PP.text x
|
||||
, case _cmd_children cmd of
|
||||
[] -> PP.empty
|
||||
cs -> PP.text "commands:" PP.$$ PP.nest 2 (PP.vcat $ commandShort <$> cs)
|
||||
, case _cmd_flags cmd of
|
||||
[] -> PP.empty
|
||||
fs -> PP.text "flags:" PP.$$ PP.nest 2 (PP.vcat $ flagShort <$> fs)
|
||||
]
|
||||
where
|
||||
commandShort :: (String, Command out) -> PP.Doc
|
||||
commandShort (s, c)
|
||||
= PP.text (s ++ ((_cmd_params c) >>= \(ParamA ps _) -> " " ++ ps))
|
||||
PP.<> case _cmd_help c of
|
||||
Nothing -> PP.empty
|
||||
Just h -> PP.text ":" PP.<+> PP.text h
|
||||
flagShort :: Flag -> PP.Doc
|
||||
flagShort f = PP.hsep (PP.text . ("-"++) . return <$> _flag_short f)
|
||||
PP.<+> PP.hsep (PP.text . ("--"++) <$> _flag_long f)
|
||||
PP.<+> case _flag_help f of
|
||||
Nothing -> PP.empty
|
||||
Just h -> PP.text h
|
||||
|
||||
ppCommandShort :: Command out -> String
|
||||
ppCommandShort cmd
|
||||
= PP.render
|
||||
$ printParent cmd
|
||||
PP.<+>
|
||||
case _cmd_flags cmd of
|
||||
[] -> PP.empty
|
||||
fs -> tooLongText 20 "[FLAGS]" $ List.unwords $ fs <&> \f ->
|
||||
"["
|
||||
++ (List.unwords $ (_flag_short f <&> \c -> ['-', c])
|
||||
++ (_flag_long f <&> \l -> "--" ++ l)
|
||||
)
|
||||
++ "]"
|
||||
PP.<+>
|
||||
case _cmd_params cmd of
|
||||
[] -> PP.empty
|
||||
ps -> PP.text $ List.unwords $ ps <&> \(ParamA s _) -> Char.toUpper <$> s
|
||||
PP.<+>
|
||||
case _cmd_children cmd of
|
||||
[] -> PP.empty
|
||||
cs -> PP.text
|
||||
$ if Maybe.isJust $ _cmd_run cmd
|
||||
then "[<" ++ intercalate "|" (fst <$> cs) ++ ">]"
|
||||
else "<" ++ intercalate "|" (fst <$> cs) ++ ">"
|
||||
where
|
||||
printParent :: Command out -> PP.Doc
|
||||
printParent c = case _cmd_mParent c of
|
||||
Nothing -> PP.empty
|
||||
Just (p, x) -> printParent p PP.<+> PP.text x
|
||||
|
||||
ppCommandShortHelp :: Command out -> String
|
||||
ppCommandShortHelp cmd
|
||||
= PP.render
|
||||
$ printParent cmd
|
||||
PP.<+>
|
||||
case _cmd_flags cmd of
|
||||
[] -> PP.empty
|
||||
fs -> tooLongText 20 "[FLAGS]" $ List.unwords $ fs <&> \f ->
|
||||
"["
|
||||
++ (List.unwords $ (_flag_short f <&> \c -> ['-', c])
|
||||
++ (_flag_long f <&> \l -> "--" ++ l)
|
||||
)
|
||||
++ "]"
|
||||
PP.<+>
|
||||
case _cmd_params cmd of
|
||||
[] -> PP.empty
|
||||
ps -> PP.text $ List.unwords $ ps <&> \(ParamA s _) -> Char.toUpper <$> s
|
||||
PP.<+>
|
||||
case _cmd_children cmd of
|
||||
[] -> PP.empty
|
||||
cs -> PP.text
|
||||
$ if Maybe.isJust $ _cmd_run cmd
|
||||
then "[<" ++ intercalate "|" (fst <$> cs) ++ ">]"
|
||||
else "<" ++ intercalate "|" (fst <$> cs) ++ ">"
|
||||
PP.<>
|
||||
case _cmd_help cmd of
|
||||
Nothing -> PP.empty
|
||||
Just h -> PP.text ":" PP.<+> PP.text h
|
||||
where
|
||||
printParent :: Command out -> PP.Doc
|
||||
printParent c = case _cmd_mParent c of
|
||||
Nothing -> PP.empty
|
||||
Just (p, x) -> printParent p PP.<+> PP.text x
|
||||
|
||||
tooLongText :: Int -- max length
|
||||
-> String -- alternative if actual length is bigger than max.
|
||||
-> String -- text to print, if length is fine.
|
||||
-> PP.Doc
|
||||
tooLongText i alt s = PP.text $ Bool.bool alt s $ null $ drop i s
|
||||
|
||||
cmds :: CmdBuilder (IO ()) ()
|
||||
cmds = do
|
||||
addCmd "echo" $ do
|
||||
help "print its parameter to output"
|
||||
str <- addParam "string" $ do
|
||||
help "the string to print"
|
||||
-- def "foo"
|
||||
impl $ do
|
||||
putStrLn str
|
||||
addCmd "hello" $ do
|
||||
help "prints some greeting"
|
||||
short <- flagAsBool $ addFlag "" ["short"] $ return ()
|
||||
name <- addParam "name" $ do
|
||||
help "your name, so you can be greeted properly"
|
||||
def "user"
|
||||
impl $ do
|
||||
if short
|
||||
then putStrLn $ "hi, " ++ name ++"!"
|
||||
else putStrLn $ "hello, " ++ name ++", welcome to colint!"
|
||||
|
||||
flagAsBool :: CmdBuilder m [a] -> CmdBuilder m Bool
|
||||
flagAsBool = fmap (not . null)
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
case cmdCheckNonStatic cmds of
|
||||
Just err -> do
|
||||
putStrLn "error building commands!!"
|
||||
putStrLn err
|
||||
Nothing -> do
|
||||
forever $ do
|
||||
putStr "> "
|
||||
hFlush stdout
|
||||
input <- System.IO.getLine
|
||||
let (errs, _, partial) = cmdGetPartial input cmds
|
||||
print partial
|
||||
putStrLn $ ppCommand $ partial
|
||||
case (errs, cmdActionPartial partial) of
|
||||
(err:_, _) -> print err
|
||||
([], eEff) -> case eEff of
|
||||
Left err -> do
|
||||
putStrLn $ "could not interpret input: " ++ err
|
||||
Right eff -> do
|
||||
eff
|
||||
|
||||
-- ----
|
||||
|
||||
instance IsParam String where
|
||||
paramParse s = do
|
||||
let s1 = dropWhile Char.isSpace s
|
||||
let (param, rest) = List.span (not . Char.isSpace) s1
|
||||
guard $ not $ null param
|
||||
pure $ (param, param, rest) -- we remove trailing whitespace, evil as we are.
|
||||
paramStaticDef = ""
|
||||
|
||||
instance IsParam () where
|
||||
paramParse s = do
|
||||
let s1 = dropWhile Char.isSpace s
|
||||
rest <- List.stripPrefix "()" s1
|
||||
pure $ ((), "()", rest)
|
||||
paramStaticDef = ()
|
|
@ -0,0 +1,158 @@
|
|||
{-# LANGUAGE DeriveFunctor #-}
|
||||
{-# LANGUAGE ScopedTypeVariables #-}
|
||||
{-# LANGUAGE ExistentialQuantification #-}
|
||||
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
|
||||
{-# LANGUAGE StandaloneDeriving #-}
|
||||
{-# LANGUAGE LambdaCase #-}
|
||||
{-# LANGUAGE DataKinds #-}
|
||||
{-# LANGUAGE TemplateHaskell #-}
|
||||
{-# LANGUAGE MonadComprehensions #-}
|
||||
|
||||
module UI.CmdParse.Monadic.Types
|
||||
( Command(..)
|
||||
, cmd_mParent
|
||||
, cmd_help
|
||||
, cmd_flags
|
||||
, cmd_params
|
||||
, cmd_children
|
||||
, cmd_run
|
||||
, flag_help
|
||||
, flag_long
|
||||
, flag_params
|
||||
, flag_unique
|
||||
, flag_short
|
||||
, param_def
|
||||
, param_help
|
||||
, emptyCommand
|
||||
, emptyParam
|
||||
, FlagParsedMap
|
||||
, FlagParsedElement(..)
|
||||
, IsParam(..)
|
||||
, IsHelpBuilder(..)
|
||||
, CmdBuilderF(..)
|
||||
, CmdBuilder
|
||||
, ParamBuilderF(..)
|
||||
, ParamBuilder
|
||||
, FlagBuilderF(..)
|
||||
, FlagBuilder
|
||||
, Flag(..)
|
||||
, Param(..)
|
||||
, ParamA(..)
|
||||
)
|
||||
where
|
||||
|
||||
|
||||
|
||||
#include "qprelude/bundle-gamma.inc"
|
||||
import Control.Monad.Free
|
||||
import qualified Control.Monad.Trans.MultiState.Strict as MultiStateS
|
||||
import Data.Unique (Unique)
|
||||
import qualified System.Unsafe as Unsafe
|
||||
|
||||
import qualified Control.Lens.TH as LensTH
|
||||
import qualified Control.Lens as Lens
|
||||
|
||||
|
||||
|
||||
data Command out = Command
|
||||
{ _cmd_mParent :: Maybe (Command out, String) -- parent command
|
||||
-- , substring that leads to $this.
|
||||
-- (kinda wonky, i know.)
|
||||
, _cmd_help :: Maybe String
|
||||
, _cmd_flags :: [Flag]
|
||||
, _cmd_params :: [ParamA]
|
||||
, _cmd_children :: [(String, Command out)]
|
||||
, _cmd_run :: Maybe out
|
||||
}
|
||||
|
||||
emptyCommand :: Command out
|
||||
emptyCommand = Command Nothing Nothing [] [] [] Nothing
|
||||
|
||||
instance Show (Command out) where
|
||||
show c = "Command help=" ++ show (_cmd_help c)
|
||||
++ " flags=" ++ show (_cmd_flags c)
|
||||
++ " params=" ++ show (_cmd_params c)
|
||||
++ " children=" ++ show (_cmd_children c)
|
||||
++ " run=" ++ case _cmd_run c of Nothing -> "Nothing"; Just{} -> "Just{..}"
|
||||
|
||||
-- class IsFlag a where
|
||||
-- flagParse :: String -> Maybe (a, String)
|
||||
-- staticDef :: a
|
||||
|
||||
data Flag = Flag
|
||||
{ _flag_unique :: Unique
|
||||
, _flag_short :: String
|
||||
, _flag_long :: [String]
|
||||
, _flag_help :: Maybe String
|
||||
, _flag_params :: [ParamA]
|
||||
}
|
||||
|
||||
instance Show Flag where
|
||||
show (Flag _ short long helpM params) = show (short, long, helpM, params) -- TODO: improve
|
||||
|
||||
type FlagParsedMap = Map Unique [FlagParsedElement]
|
||||
|
||||
data FlagParsedElement = FlagParsedElement [String]
|
||||
deriving Show
|
||||
|
||||
data ParamA = forall p . (IsParam p, Show p) => ParamA String (Param p)
|
||||
|
||||
deriving instance Show ParamA
|
||||
|
||||
class IsParam a where
|
||||
paramParse :: String -> Maybe (a, String, String) -- value, representation, rest
|
||||
paramStaticDef :: a
|
||||
|
||||
data Param a = Param
|
||||
{ _param_help :: Maybe String
|
||||
, _param_def :: Maybe a
|
||||
}
|
||||
|
||||
emptyParam :: Param a
|
||||
emptyParam = Param Nothing Nothing
|
||||
|
||||
deriving instance Show a => Show (Param a)
|
||||
|
||||
data CmdBuilderF out a
|
||||
= CmdBuilderHelp String a
|
||||
| forall b . CmdBuilderFlag Unique String [String] (FlagBuilder b) ([b] -> a)
|
||||
| forall p . (Show p, IsParam p) => CmdBuilderParam String (ParamBuilder p ()) (p -> a)
|
||||
| CmdBuilderChild String (CmdBuilder out ()) a
|
||||
| CmdBuilderRun out -- TODO: why do we "abort" here? (i.e. no `a`)
|
||||
-- this is not actually enforced when writing
|
||||
-- CmdBuilders, is it? if it is not, this would result
|
||||
-- in rather nasty silent-ignoring.
|
||||
|
||||
deriving instance Functor (CmdBuilderF out)
|
||||
|
||||
instance Show a => Show (CmdBuilderF out a) where
|
||||
show (CmdBuilderHelp s x) = "(CmdBuilderHelp " ++ show s ++ " " ++ show x ++ ")"
|
||||
show (CmdBuilderFlag _ shorts longs _ _) = "(CmdBuilderFlag -" ++ shorts ++ " " ++ show longs ++ ")"
|
||||
show (CmdBuilderParam s _ _) = "(CmdBuilderParam " ++ s ++ ")"
|
||||
show (CmdBuilderChild s _ _) = "(CmdBuilderChild " ++ s ++ ")"
|
||||
show (CmdBuilderRun _) = "CmdBuilderRun"
|
||||
|
||||
type CmdBuilder out = Free (CmdBuilderF out)
|
||||
|
||||
data FlagBuilderF a
|
||||
= FlagBuilderHelp String a
|
||||
| forall p . (Show p, IsParam p) => FlagBuilderParam String (ParamBuilder p ()) (p -> a)
|
||||
|
||||
deriving instance Functor FlagBuilderF
|
||||
|
||||
type FlagBuilder = Free FlagBuilderF
|
||||
|
||||
data ParamBuilderF p a
|
||||
= ParamBuilderHelp String a
|
||||
| ParamBuilderDef p a
|
||||
|
||||
deriving instance Functor (ParamBuilderF p)
|
||||
|
||||
type ParamBuilder p = Free (ParamBuilderF p)
|
||||
|
||||
class IsHelpBuilder m where
|
||||
help :: String -> m ()
|
||||
|
||||
Lens.makeLenses ''Command
|
||||
Lens.makeLenses ''Flag
|
||||
Lens.makeLenses ''Param
|
Loading…
Reference in New Issue