packages feed

butcher-1.3.3.0: src/UI/Butcher/Applicative.hs

{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE DataKinds #-}

module UI.Butcher.Applicative where



#include "prelude.inc"

import           UI.Butcher.Applicative.Types
import           Control.Applicative.Free
import qualified Text.PrettyPrint              as PP


data DescState = DescState
  { parts    :: Deque PartDesc
  , children :: Deque (String, CommandDesc)
  , help     :: Maybe PP.Doc
  , reorder  :: Maybe (Deque PartDesc)
  }

toCmdDesc :: forall out . CmdParser out out -> CommandDesc
toCmdDesc cmdParser =
  let final = appEndo (runAp_ f cmdParser) initialState
  in  CommandDesc { _cmd_mParent    = Nothing
                  , _cmd_synopsis   = Nothing
                  , _cmd_help       = help final
                  , _cmd_parts      = Data.Foldable.toList $ parts final
                  , _cmd_children   = Data.Foldable.toList $ children final
                  , _cmd_visibility = Visible
                  }
 where
  f :: CmdParserF out a -> Endo (DescState)
  f x = Endo $ \s -> case x of
    CmdParserHelp     doc _         -> s { help = Just doc }
    CmdParserSynopsis _   _         -> error "todo"
    CmdParserPeekInput _            -> s
    CmdParserPart desc _ _          -> appendPart s desc
    CmdParserPartMany _ desc _ _    -> appendPart s desc
    CmdParserPartInp desc _ _       -> appendPart s desc
    CmdParserPartManyInp _ desc _ _ -> appendPart s desc
    CmdParserChild name parser _    -> appendChild s $ (name, toCmdDesc parser)
    CmdParserReorderStart _         -> s { reorder = reorder s <|> Just empty }
    CmdParserReorderStop  _         -> case reorder s of
      Nothing -> s
      Just ps -> s { parts = parts s <> ps, reorder = Nothing }
   where
    appendPart s p = s { parts = Deque.snoc p (parts s) }
    appendChild s c = s { children = Deque.snoc c (children s) }
  initialState = DescState { parts    = mempty
                           , children = mempty
                           , help     = Nothing
                           , reorder  = mempty
                           }

data ParserState out = ParserState
  { p_parts    :: Deque PartDesc
  , p_children :: Deque (String, CommandDesc)
  , p_help     :: Maybe PP.Doc
  , p_reorder  :: Maybe (Deque PartDesc)
  , p_input    :: Input
  }

runCmdParser
  :: Input -> CmdParser out out -> (CommandDesc, Either ParsingError out)
runCmdParser initialInput initialParser =
  let topDesc = toCmdDesc initialParser
  in  (topDesc, go initialInput topDesc initialParser)
 where
  go :: Input -> CommandDesc -> CmdParser out out -> Either ParsingError out
  go input desc parser =
    -- TODO use x?
    let initialState = ParserState { p_parts    = mempty
                                   , p_children = mempty
                                   , p_help     = Nothing
                                   , p_reorder  = mempty
                                   , p_input    = input
                                   }
        (outE, _x) = StateS.runState (iter parser) initialState
    in  outE
   where
    iter
      :: CmdParser out out
      -> StateS.State (ParserState out) (Either ParsingError out)
    iter = \case
      Pure x                          -> pure $ Right x
      Ap (CmdParserHelp     _ x) next -> continue next x
      Ap (CmdParserSynopsis _ x) next -> continue next x
      Ap (CmdParserPeekInput f ) next -> do
        s <- StateS.get
        iter $ next <&> \g -> g $ f (inputToString $ p_input s)
      Ap (CmdParserPart _ parseF f) next -> parseWithStr
        parseF
        (\x -> iter $ next <&> \g -> g (f x))
        (\s -> pure $ Left $ ParsingError { _pe_messages  = ["could not parse"]
                                          , _pe_remaining = p_input s
                                          }
        )
      Ap (CmdParserPartMany _ _ parseF f) next -> do
        let loop = do
              dropSpaces
              parseWithStr
                parseF
                (\x -> do
                  more <- loop
                  pure (x : more)
                )
                (\_ -> pure [])
        ps <- loop
        iter $ next <&> \g -> g (f ps)
      Ap (CmdParserPartInp _ parseF f) next -> do
        s <- StateS.get
        case parseF (p_input s) of
          Just (x, rest) -> do
            StateS.put s { p_input = rest }
            iter $ next <&> \g -> g (f x)
          Nothing -> pure $ Left $ ParsingError
            { _pe_messages  = ["could not parse"]
            , _pe_remaining = p_input s
            }
      Ap (CmdParserPartManyInp _ _ parseF f) next -> do
        let loop = do
              dropSpaces
              s <- StateS.get
              case parseF (p_input s) of
                Just (x, rest) -> do
                  StateS.put s { p_input = rest }
                  (x :) <$> loop
                Nothing -> pure $ []
        ps <- loop
        iter $ next <&> \g -> g (f ps)
      Ap (CmdParserChild name childParser x) next -> do
        dropSpaces
        s <- StateS.get
        let childDesc = case find ((== name) . fst) (_cmd_children desc) of
              Just (_, d) -> d
              Nothing     -> error "inconsistent child name map"
        case p_input s of
          InputString str -> if
            | str == name -> do
              pure $ go (InputString "") childDesc childParser
            |
-- TODO str prefix
              otherwise -> continue next x
          InputArgs (a1 : ar) | a1 == name -> do
            pure $ go (InputArgs ar) childDesc childParser
          InputArgs{} -> continue next x
      Ap (CmdParserReorderStart _) next -> error "TODO" next
      Ap (CmdParserReorderStop  _) next -> error "TODO" next
     where
      continue next x = iter (($x) <$> next)
      parseWithStr f successF failF = do
        s <- StateS.get
        case p_input s of
          InputString str -> case f str of
            Just (x, rest) -> do
              StateS.put s { p_input = InputString rest }
              successF x
            Nothing -> failF s
          InputArgs (a1 : ar) -> case f a1 of
            Just (x, "") -> do
              StateS.put s { p_input = InputArgs ar }
              successF x
            _ -> failF s
          InputArgs [] -> failF s

  inputToString :: Input -> String
  inputToString (InputString s ) = s
  inputToString (InputArgs   ss) = List.unwords ss
  dropSpaces = do
    st <- StateS.get
    case p_input st of
      InputString s ->
        StateS.put $ st { p_input = InputString $ dropWhile Char.isSpace s }
      InputArgs{} -> return ()

addCmdPart
  :: Typeable p => PartDesc -> (String -> Maybe (p, String)) -> CmdParser out p
addCmdPart p f = liftAp $ CmdParserPart p f id

addCmdPartMany
  :: Typeable p
  => ManyUpperBound
  -> PartDesc
  -> (String -> Maybe (p, String))
  -> CmdParser out [p]
addCmdPartMany b p f = liftAp $ CmdParserPartMany b p f id

addCmdPartInp
  :: Typeable p => PartDesc -> (Input -> Maybe (p, Input)) -> CmdParser out p
addCmdPartInp p f = liftAp $ CmdParserPartInp p f id

addCmdPartManyInp
  :: Typeable p
  => ManyUpperBound
  -> PartDesc
  -> (Input -> Maybe (p, Input))
  -> CmdParser out [p]
addCmdPartManyInp b p f = liftAp $ CmdParserPartManyInp b p f id

data Param p = Param
  { _param_default     :: Maybe p
  , _param_help        :: Maybe PP.Doc
  , _param_suggestions :: Maybe [CompletionItem]
  }

addReadParam
  :: forall out a
   . (Typeable a, Show a, Text.Read.Read a)
  => String -- ^ paramater name, for use in usage/help texts
  -> Param a -- ^ properties
  -> CmdParser out a
addReadParam name par = addCmdPart desc parseF
 where
  desc :: PartDesc
  desc =
    addSuggestion (_param_suggestions par)
      $ (maybe id PartWithHelp $ _param_help par)
      $ (maybe id (PartDefault . show) $ _param_default par)
      $ PartVariable name
  parseF :: String -> Maybe (a, String)
  parseF s = case Text.Read.reads s of
    ((x, ' ' : r) : _) -> Just (x, dropWhile Char.isSpace r)
    ((x, []     ) : _) -> Just (x, [])
    _                  -> _param_default par <&> \x -> (x, s)

addSuggestion :: Maybe [CompletionItem] -> PartDesc -> PartDesc
addSuggestion Nothing     = id
addSuggestion (Just sugs) = PartSuggestion sugs


data Flag a = Flag
  { flag_name :: String
  , flag_short :: String
  , flag_long :: [String]
  , flag_opts :: FlagOpts a
  , flag_parser :: String -> Maybe (Identity a, String)
  }

data FlagOpts a = FlagOpts
  { _flag_help       :: Maybe PP.Doc
  , _flag_default    :: Maybe a
  , _flag_visibility :: Visibility
  }

traverseFlags
  :: (Covered c, CTraversable Typeable (c 'Wrap))
  => c 'Wrap Flag
  -> CmdParser out (c 'Bare Identity)
traverseFlags k = do
  r <- cTraverse
    (Proxy @Typeable)
    (\f -> addCmdPart (PartVariable (flag_name f)) (flag_parser f))
    k
  pure $ unwrap r

test :: IO ()
test = do
  let parser = do
        arg :: Int <- addReadParam
          "arg"
          Param { _param_default     = Nothing
                , _param_help        = Nothing
                , _param_suggestions = Nothing
                }
        pure $ print arg
  let (desc, eOut) = runCmdParser (InputArgs ["42"]) parser
  print desc
  case eOut of
    Left err -> do
      putStrLn "parsing error"
      print err
    Right f -> f

-- butcherMain :: ButcherA (IO ()) -> IO ()
-- 
-- type ButcherA out = Writer [ButcherCmd out] ()
-- type ButcherCmd = Ap ButcherCmdF out
-- data ButcherCmdF a
--   = ButcherCmdHelp String (() -> a)
--   | ButcherCmdParamString (String -> a)


{-
stringArg :: UnwrapField ('Just Arg) String
stringArg = mempty

argDef :: a -> UnwrapField ('Just Arg) a
argDef x = mempty { _arg_def = Just x }

fromUnordered
  :: (Covered h, CTraversable (h Wrap))
  => String
  -> h Wrap Arg
  -> CmdParserF out (h Plain Identity)

data Arg a = Arg
  { _arg_def  :: Maybe a
  , _arg_help :: Maybe String
  }

instance Monoid (Arg a) where ..

data MyArgs s f = MyArgs
  { greeting :: Wear s f String
  , name     :: Wear s f String
  }

main = butcherMain $ do
  args <- fromUnordered MyArgs
    { greeting = stringArg <> argDef "hi"
    , name     = stringArg
    }
  pure do
    putStrLn $ greeting args ++ ", " ++ name args ++ "!"
-}