packages feed

symantic-cli-2.2.4.20190701: Symantic/CLI/Help.hs

{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE UndecidableInstances #-} -- for From (Word *) d
module Symantic.CLI.Help where

import Control.Applicative (Applicative(..))
import Data.Bool
import Data.Foldable (null)
import Data.Function (($), (.))
import Data.Functor (Functor(..), (<$>))
import Data.Maybe (Maybe(..), maybe, isJust)
import Data.Monoid (Monoid(..))
import Data.Semigroup (Semigroup(..))
import Text.Show (Show(..))
import Data.Tree as Tree
import qualified Symantic.Document as Doc

import Symantic.CLI.API
import Symantic.CLI.Schema as Schema

-- * Type 'Help'
data Help d f k
 =   Help
 {   help_result :: HelpInh d -> HelpResult d
     -- ^ The 'HelpResult' of the current symantic.
 ,   help_schema :: Schema d f k
     -- ^ The 'Schema' of the current symantic.
 }

runHelp :: Docable d => HelpInh d -> Help d f k -> d
runHelp def (Help h _p) = runHelpNodes def (h def) <> Doc.newline

docHelp :: Docable d => Doc.Indentable d => Docable d => Help d f k -> d
docHelp = runHelp defHelpInh

coerceHelp :: Help d f k -> Help d f' k'
coerceHelp Help{help_schema, ..} = Help
 { help_schema = Schema.coerceSchema help_schema
 , ..
 }

-- ** Type 'HelpInh'
-- | Configuration inherited top-down.
data HelpInh d
 =   HelpInh
 {   helpInh_message :: !(Maybe d)
     -- ^ The message inherited from 'help's.
 ,   helpInh_command_indent :: !Doc.Indent
     -- ^ 'Doc.Indent'ation for 'command's.
 ,   helpInh_tag_indent :: !Doc.Indent
     -- ^ 'Doc.Indent'ation for 'Tag's.
 ,   helpInh_schema :: !(SchemaInh d)
     -- ^ The inherited 'SchemaInh' for 'runSchema'.
 ,   helpInh_helpless_options :: !Bool
     -- ^ Whether to include options without help in the listing.
 ,   helpInh_command_rule :: !Bool
     -- ^ Whether to print the name of the rule.
 ,   helpInh_full :: !Bool
     -- ^ Whether to print full help.
 }

defHelpInh :: Docable d => HelpInh d
defHelpInh = HelpInh
 { helpInh_message          = Nothing
 , helpInh_command_indent   = 2
 , helpInh_tag_indent       = 16
 , helpInh_schema           = defSchemaInh
 , helpInh_helpless_options = False
 , helpInh_command_rule     = False
 , helpInh_full             = True
 }

-- ** Type 'HelpResult'
type HelpResult d = Tree.Forest (HelpNode, d)

defHelpResult :: Monoid d => HelpResult d
defHelpResult = mempty

-- *** Type 'HelpNode'
data HelpNode
 =   HelpNode_Message
 |   HelpNode_Rule
 |   HelpNode_Command
 |   HelpNode_Tag
 |   HelpNode_Env
 deriving Show

runHelpNode ::
 Monoid d =>
 Docable d =>
 Tree (HelpNode, d) -> d
runHelpNode (Tree.Node (_n,d) _ts) = d

-- | Introduce 'Doc.newline' according to the 'HelpNode's
-- put next to each others.
runHelpNodes ::
 Monoid d =>
 Docable d =>
 HelpInh d ->
 Tree.Forest (HelpNode, d) -> d
runHelpNodes _inh [] = mempty
runHelpNodes inh ( t0@(Tree.Node _ t0s)
                 : t1@(Tree.Node (HelpNode_Command, _) _) : ts ) =
	runHelpNode t0 <>
	Doc.newline <>
	(if null t0s || not (helpInh_full inh) then mempty else Doc.newline) <>
	runHelpNodes inh (t1:ts)
runHelpNodes inh ( t0@(Tree.Node (HelpNode_Tag, _) _)
                 : t1@(Tree.Node (HelpNode_Tag, _) _) : ts ) =
	runHelpNode t0 <>
	Doc.newline <>
	runHelpNodes inh (t1:ts)
runHelpNodes inh ( t0@(Tree.Node (HelpNode_Rule, _) t0s)
                 : t1@(Tree.Node (_, _) _) : ts ) =
	runHelpNode t0 <>
	Doc.newline <>
	(if null t0s || not (helpInh_full inh) then mempty else Doc.newline) <>
	runHelpNodes inh (t1:ts)
runHelpNodes inh ( t0@(Tree.Node (HelpNode_Message, _) _)
                 : t1 : ts ) =
	runHelpNode t0 <>
	Doc.newline <>
	Doc.newline <>
	runHelpNodes inh (t1:ts)
runHelpNodes inh ( t0 : t1@(Tree.Node (HelpNode_Tag, _) _) : ts ) =
	runHelpNode t0 <>
	Doc.newline <>
	Doc.newline <>
	runHelpNodes inh (t1:ts)
runHelpNodes inh ( t0 : t1@(Tree.Node (HelpNode_Env, _) _) : ts ) =
	runHelpNode t0 <>
	Doc.newline <>
	Doc.newline <>
	runHelpNodes inh (t1:ts)
runHelpNodes inh ( t0 : t1@(Tree.Node (HelpNode_Rule, _) _) : ts ) =
	runHelpNode t0 <>
	Doc.newline <>
	runHelpNodes inh (t1:ts)
runHelpNodes inh (t:ts) = runHelpNode t <> runHelpNodes inh ts

instance Semigroup d => Semigroup (Help d f k) where
	Help hx px <> Help hy py = Help (hx<>hy) (px<>py)
instance Monoid d => Monoid (Help d f k) where
	mempty  = Help mempty mempty
	mappend = (<>)
{-
instance (Semigroup d, IsString d) => IsString (Help d e s a) where
	fromString "" = Help $ \_ro -> Nothing
	fromString s  = Help $ \_ro -> Just $ fromString s
instance Show (Help Doc.Term e s a) where
	show = TL.unpack . Doc.textTerm . runHelp
instance Docable d => Functor (Help d f) where
	f <$$> Help h s = Help h (f<$$>s)
-}
instance Docable d => App (Help d) where
	Help hf pf <.> Help hx px = Help (hf<>hx) (pf<.>px)
instance Docable d => Alt (Help d) where
	Help hl pl <!> Help hr pr = Help (hl<>hr) (pl<!>pr)
	Help hl pl `alt` Help hr pr = Help (hl<>hr) (pl`alt`pr)
	opt (Help h s) = Help h (opt s)
	{-
	try (Help h s) = Help h (try s)
	choice hs = Help (mconcat $ help_result <$> hs) (choice (help_schema <$> hs))
	option a (Help h s) = Help h (option a s)
	-}
instance Docable d => Permutable (Help d) where
	type Permutation (Help d) = HelpPerm d
	runPermutation (HelpPerm h s) = Help h $ runPermutation s
	toPermutation   (Help h s) = HelpPerm h $ toPermutation s
	toPermDefault a (Help h s) = HelpPerm h $ toPermDefault a s
instance Pro (Help d) where
	dimap a2b b2a (Help h s) = Help h $ dimap a2b b2a s
instance Docable d => AltApp (Help d) where
	many0 (Help h s) = Help h (many0 s)
	many1 (Help h s) = Help h (many1 s)
instance Docable d => CLI_Var (Help d) where
	type VarConstraint (Help d) a = ()
	var' n  = Help mempty (var' n)
	just a  = Help mempty (just a)
	nothing = Help mempty nothing
instance Docable d => CLI_Env (Help d) where
	type EnvConstraint (Help d) a = ()
	env' n =
		Help (\inh ->
			let ts =
				if helpInh_full inh
				then maybe [] (pure . pure . (HelpNode_Message,)) (helpInh_message inh)
				else [] in
			let d =
				Doc.breakfill (helpInh_tag_indent inh)
				 (Doc.bold (Doc.green (Doc.from (Doc.Word n)))
					<> Doc.space)
				<> (if null ts then mempty else Doc.space)
				<> Doc.align (runHelpNodes inh ts)
				in
			[ Tree.Node (HelpNode_Env, d) ts ]
		 ) schema
		where schema = env' n
instance Docable d => CLI_Command (Help d) where
	-- type CommandConstraint (Help d) a = ()
	command n (Help h s) =
		Help (\inh ->
			let ts =
				(if helpInh_full inh
				then maybe [] (pure . pure . (HelpNode_Message,)) (helpInh_message inh)
				else []) <>
				h inh
				 { helpInh_message      = Nothing
				 , helpInh_command_rule = True
				 } in
			let d =
				(if not (null n) && helpInh_command_rule inh
				then ref<>Doc.space<>"::= " else mempty)
				<> Schema.runSchema schema (helpInh_schema inh)
				<> (if null ts || not (helpInh_full inh) then mempty else Doc.newline)
				<> Doc.incrIndent (helpInh_command_indent inh)
				 ((if null ts then mempty else Doc.newline) <> runHelpNodes inh ts)
			in
			[ Tree.Node (HelpNode_Command, d) ts ]
		 ) schema
		where
		schema = command n s
		ref =
			Doc.bold $
			Doc.angles $
			Doc.magentaer $
			Doc.from (Doc.Word n)
instance Docable d => CLI_Tag (Help d) where
	type TagConstraint (Help d) a = ()
	tagged n (Help h s) =
		Help (\inh ->
			if (isJust (helpInh_message inh)
			|| helpInh_helpless_options inh)
			&& helpInh_full inh
			then
				let ts =
					maybe [] (pure . pure . (HelpNode_Message,)) (helpInh_message inh) <>
					h inh{helpInh_message=Nothing} in
				let d =
					Doc.breakfill (helpInh_tag_indent inh)
					 (Doc.bold $
						Schema.runSchema schema (helpInh_schema inh)
						<> Doc.space) -- FIXME: space is not always needed
					<> (if null ts then mempty else Doc.space)
					<> Doc.align (runHelpNodes inh ts)
					in
				[ Tree.Node (HelpNode_Tag, d) ts ]
			else []
		 ) schema
		where schema = tagged n s
	endOpts = Help mempty endOpts
instance Docable d => CLI_Help (Help d) where
	type HelpConstraint (Help d) d' = d ~ d'
	help msg (Help h s) = Help
	 (\inh -> h inh{helpInh_message=Just msg})
	 (help msg s)
	program n (Help h s) =
		Help (\inh ->
			let ts =
				(if helpInh_full inh
				then maybe [] (pure . pure . (HelpNode_Message,)) (helpInh_message inh)
				else []) <>
				h inh
				 { helpInh_message      = Nothing
				 , helpInh_command_rule = True
				 } in
			let d =
				Schema.runSchema schema (helpInh_schema inh)
				<> (if null ts {-|| not (helpInh_full inh)-} then mempty else Doc.newline)
				<> Doc.incrIndent
				 (helpInh_command_indent inh)
				 ((if null ts then mempty else Doc.newline) <> runHelpNodes inh ts)
			in
			[ Tree.Node (HelpNode_Rule, d) ts ]
		 ) schema
		where
		schema = program n s
	rule n (Help h s) =
		Help (\inh ->
			let ts =
				(if helpInh_full inh
				then maybe [] (pure . pure . (HelpNode_Message,)) (helpInh_message inh)
				else []) <>
				h inh
				 { helpInh_message      = Nothing
				 , helpInh_command_rule = True
				 } in
			let d =
				ref<>Doc.space<>"::= "
				<> Schema.runSchema schema (helpInh_schema inh)
				<> (if null ts || not (helpInh_full inh) then mempty else Doc.newline)
				<> Doc.incrIndent
				 (helpInh_command_indent inh)
				 ((if null ts then mempty else Doc.newline) <> runHelpNodes inh ts)
			in
			[ Tree.Node (HelpNode_Rule, d) ts ]
		 ) schema
		where
		schema = rule n s
		ref =
			Doc.bold $
			Doc.angles $
			Doc.magentaer $
			Doc.from (Doc.Word n)
type HelpResponseArgs = SchemaResponseArgs
instance Docable d => CLI_Response (Help d) where
	type ResponseConstraint (Help d) a = () -- ResponseConstraint (Schema d)
	type ResponseArgs (Help d) a = SchemaResponseArgs a -- ResponseArgs (Schema d) a
	type Response (Help d) = () -- Response (Schema d)
	response' ::
	 forall a repr.
	 repr ~ Help d =>
	 ResponseConstraint repr a =>
	 repr (ResponseArgs repr a)
	      (Response repr)
	response' = Help mempty $ response' @(Schema d) @a

{-
instance Docable d => Sym_AltApp (Help d) where
	many (Help h s) = Help h (many s)
	some (Help h s) = Help h (many s)
-}

-- * Type 'HelpPerm'
data HelpPerm d k a
 =   HelpPerm (HelpInh d -> HelpResult d)
              (SchemaPerm d k a)
instance Functor (HelpPerm d k) where
	f`fmap`HelpPerm h ps = HelpPerm h (f<$>ps)
instance Applicative (HelpPerm d k) where
	pure a = HelpPerm mempty (pure a)
	HelpPerm fh f <*> HelpPerm xh x =
		HelpPerm (fh<>xh) (f<*>x)
instance Docable d => CLI_Help (HelpPerm d) where
	type HelpConstraint (HelpPerm d) d' = d ~ d'
	help msg (HelpPerm h s) = HelpPerm
	 (\inh -> h inh{helpInh_message=Just msg})
	 (help msg s)
	program n (HelpPerm h s) = HelpPerm
	 (help_result $ program n (Help h (runPermutation s)))
	 (rule n s)
	rule n (HelpPerm h s) = HelpPerm
	 (help_result $ rule n (Help h (runPermutation s)))
	 (rule n s)