packages feed

mangrove-cli-0.1.0.0: src/Mangrove/Scheme/Sub.hs

{-# LANGUAGE DeriveFunctor     #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeFamilies      #-}

{-|
Module      : Mangrove.Scheme.Sub
Copyright   : (c) Quytelda Kahja, 2026
License     : BSD-3-Clause

A parsing scheme for Unix-style subarguments (i.e. arguments passed as
subarguments to an option).
-}
module Mangrove.Scheme.Sub
  ( -- * Types
    SubScheme(..)
  , Token(..)
  , SubParser

    -- * Properties
  , hasSubOptions
  ) where

import           Control.Applicative
import           Data.Text           (Text)

import           Mangrove.Parser
import           Mangrove.Resolve
import           Mangrove.Separable
import           Mangrove.Text
import           Mangrove.TextParser
import           Mangrove.Valency

-- | Parsers for subarguments of an option (e.g. @--option key=value@).
data SubScheme r
  = Parameter (TextParser r) -- ^ Parses freeform arguments
  | Option Text (TextParser r) -- ^ Suboptions have the form "KEY=VALUE"
  deriving (Functor)

instance Valency SubScheme where
  valency _ = Just 1

instance Resolve SubScheme where
  resolve (Parameter (TextParser hint _)) =
    ExpectedError [render hint]
  resolve (Option key (TextParser hint _)) =
    ExpectedError [render key <> "=" <> render hint]

instance Separable SubScheme where
  separate s = Exhibit (Just s) []

instance Scheme SubScheme where
  data Token SubScheme
    = SubAssoc Text Text -- ^ A "KEY=VALUE" argument
    | SubArgument Text -- ^ A standard freeform argument
    deriving (Eq, Show)

  delimiter _ = ','

  activate parser = do
    next <- peek
    escaped <- getEscaped
    case (parser, keyEqualsValue next) of
      (Parameter tp, Nothing) ->
        withContext (SubArgument next) $
        pop_ *> runTextParser tp next
      (Option _ _, Nothing) ->
        empty
      (Option key tp, Just (k,v))
        | not escaped && key == k ->
          withContext (SubAssoc k v) $
          pop_ *> runTextParser tp v
      (Parameter tp, Just _)
        | escaped ->
          withContext (SubArgument next) $
          pop_ *> runTextParser tp next
        | otherwise ->
          empty
      _ -> empty

  usageInfo (Parameter tp)  = render $ parserHint tp
  usageInfo (Option key tp) = render key <> "=" <> render (parserHint tp)

instance Render (Token SubScheme) where
  render (SubAssoc key value) = render key <> "=" <> render value
  render (SubArgument value)  = render value

-- | Type alias for SubScheme parse trees.
type SubParser = ParseTree SubScheme

-- | Check whether a parse tree contains any suboption parsers. This
-- allows us to determine if we need to parse "KEY=VALUE" pairs.
hasSubOptions :: ParseTree SubScheme r -> Bool
hasSubOptions EmptyNode                 = False
hasSubOptions (ValueNode _)             = False
hasSubOptions (ParseNode (Parameter _)) = False
hasSubOptions (ParseNode (Option _ _))  = True
hasSubOptions (ProdNode _ l r)          = hasSubOptions l || hasSubOptions r
hasSubOptions (SumNode l r)             = hasSubOptions l || hasSubOptions r
hasSubOptions (ManyNode _ tree)         = hasSubOptions tree