1 {-# LANGUAGE ConstraintKinds #-}
2 {-# LANGUAGE NoMonomorphismRestriction #-}
3 {-# LANGUAGE Rank2Types #-}
4 {-# LANGUAGE OverloadedStrings #-}
5 {-# LANGUAGE UndecidableInstances #-} -- for From (Word *) d
6 module Symantic.CLI.Schema where
8 import Control.Applicative (Applicative(..))
10 import Data.Char (Char)
11 import Data.Function (($), (.), id)
12 import Data.Functor (Functor(..), (<$>))
13 import Data.Maybe (Maybe(..), fromMaybe, catMaybes)
14 import Data.Monoid (Monoid(..))
15 import Data.Semigroup (Semigroup(..))
16 import Data.String (String, IsString(..))
17 import Data.Text (Text)
18 import Text.Show (Show(..))
19 import qualified Data.List as List
20 import qualified Data.Text.Lazy as TL
21 import qualified Data.Text.Lazy.Builder as TLB
22 import qualified Symantic.Document as Doc
24 import Symantic.CLI.API
25 import Symantic.CLI.Fixity
29 = Schema { unSchema :: SchemaInh d -> Maybe d }
31 runSchema :: Monoid d => Schema d f k -> SchemaInh d -> d
32 runSchema (Schema s) = fromMaybe mempty . s
34 docSchema :: Monoid d => Docable d => Schema d f k -> d
35 docSchema s = runSchema s defSchemaInh
37 coerceSchema :: Schema d f k -> Schema d f' k'
38 coerceSchema Schema{..} = Schema{..}
41 type Doc = Doc.AnsiText (Doc.Plain TLB.Builder)
53 , Doc.From (Doc.Word Char) d
54 , Doc.From (Doc.Word Text) d
55 , Doc.From (Doc.Word String) d
58 -- ** Type 'SchemaInh'
59 -- | Inherited top-down.
62 { schemaInh_op :: (Infix, Side) -- ^ Parent operator.
63 , schemaInh_define :: Bool -- ^ Whether to print a definition, or not.
67 defSchemaInh :: Docable d => SchemaInh d
68 defSchemaInh = SchemaInh
69 { schemaInh_op = (infixN0, SideL)
70 , schemaInh_define = True
71 , schemaInh_or = docOrH
74 pairIfNeeded :: Docable d => (Infix, Side) -> Infix -> d -> d
75 pairIfNeeded opInh op =
76 if needsParenInfix opInh op
77 then Doc.align . Doc.parens
80 instance Semigroup d => Semigroup (Schema d f k) where
81 Schema x <> Schema y = Schema $ x <> y
82 instance (Semigroup d, Monoid d) => Monoid (Schema d f k) where
83 mempty = Schema mempty
85 instance (Semigroup d, IsString d) => IsString (Schema d f k) where
86 fromString "" = Schema $ \_inh -> Nothing
87 fromString s = Schema $ \_inh -> Just $ fromString s
88 instance Show (Schema Doc a k) where
96 docOrH, docOrV :: Doc.Spaceable d => Doc.From (Doc.Word Char) d => d
97 docOrH = Doc.space <> Doc.from (Doc.Word '|') <> Doc.space
98 docOrV = Doc.newline <> Doc.from (Doc.Word '|') <> Doc.space
101 instance Docable d => Functor (Schema d f) where
102 _f `fmap` Schema x = Schema $ \inh ->
103 pairIfNeeded (schemaInh_op inh) op <$>
104 x inh{schemaInh_op=(op, SideR)}
108 instance Docable d => App (Schema d) where
109 Schema f <.> Schema x = Schema $ \inh ->
110 case f inh{schemaInh_op=(op, SideL)} of
111 Nothing -> x inh{schemaInh_op=(op, SideR)}
113 case x inh{schemaInh_op=(op, SideR)} of
115 Just xd -> Just $ pairIfNeeded (schemaInh_op inh) op $ fd <> Doc.space <> xd
118 instance Docable d => Alt (Schema d) where
119 l <!> r = Schema $ \inh ->
120 -- NOTE: first try to see if both sides are 'Just',
121 -- otherwise does not change the inherited operator context.
122 case (unSchema l inh, unSchema r inh) of
123 (Nothing, Nothing) -> Nothing
124 (Just ld, Nothing) -> Just ld
125 (Nothing, Just rd) -> Just rd
126 (Just{}, Just{}) -> Just $
127 if needsParenInfix (schemaInh_op inh) op
129 -- NOTE: when parenthesis are needed
130 -- first try to fit the alternative on a single line,
131 -- otherwise align them on multiple lines.
134 -- Doc.withBreakable Nothing $
136 { schemaInh_op=(op, SideL)
137 , schemaInh_or=docOrH } <>
140 { schemaInh_op=(op, SideR)
141 , schemaInh_or=docOrH })
146 { schemaInh_op=(op, SideL)
147 , schemaInh_or=docOrV } <>
150 { schemaInh_op=(op, SideR)
151 , schemaInh_or=docOrV } <>
154 -- NOTE: when parenthesis are NOT needed
155 -- just concat alternatives using the inherited separator
156 -- (either horizontal or vertical).
157 runSchema l inh{schemaInh_op=(op, SideL)} <>
159 runSchema r inh{schemaInh_op=(op, SideR)}
160 where op = infixB SideL 2
161 alt x y = coerceSchema $ coerceSchema x <!> coerceSchema y
162 opt s = Schema $ \inh -> Just $
164 runSchema s inh{schemaInh_op=(op, SideL)}
166 instance Pro (Schema d) where
167 dimap _a2b _b2a = coerceSchema
168 instance Docable d => AltApp (Schema d) where
169 many0 s = Schema $ \inh -> Just $
170 runSchema s inh{schemaInh_op=(op, SideL)}<>"*"
172 many1 s = Schema $ \inh -> Just $
173 runSchema s inh{schemaInh_op=(op, SideL)}<>"..."
175 instance Docable d => CLI_Command (Schema d) where
176 -- type CommandConstraint (Schema d) a = ()
177 command n s = Schema $ \inh -> Just $
178 if schemaInh_define inh || List.null n
181 (fromString n <.> coerceSchema s)
182 inh{schemaInh_define = False}
189 Doc.from (Doc.Word n)
190 instance Docable d => CLI_Var (Schema d) where
191 type VarConstraint (Schema d) a = ()
192 var' n = Schema $ \_inh -> Just $
193 Doc.underline $ Doc.from $ Doc.Word n
194 just _ = Schema $ \_inh -> Nothing
195 nothing = Schema $ \_inh -> Nothing
196 instance Docable d => CLI_Env (Schema d) where
197 type EnvConstraint (Schema d) a = ()
198 env' _n = Schema $ \_inh -> Nothing
199 -- NOTE: environment variables are not shown in the schema,
201 instance Docable d => CLI_Tag (Schema d) where
202 type TagConstraint (Schema d) a = ()
203 tagged n r = Schema $ \inh ->
204 unSchema (prefix n <.> r) inh
207 Tag s l -> prefix (TagShort s)<>"|"<>prefix (TagLong l)
208 TagShort s -> fromString ['-', s]
209 TagLong l -> fromString ("--"<>l)
210 endOpts = Schema $ \_inh -> Just $ Doc.brackets "--"
211 instance Docable d => CLI_Help (Schema d) where
212 type HelpConstraint (Schema d) d' = d ~ d'
214 program n s = Schema $ \inh -> Just $
216 (fromString n <.> coerceSchema s)
217 inh{schemaInh_define = False}
218 rule n s = Schema $ \inh -> Just $
219 if schemaInh_define inh
220 then runSchema s inh{schemaInh_define=False}
227 Doc.from (Doc.Word n)
228 data SchemaResponseArgs a
229 instance Docable d => CLI_Response (Schema d) where
230 type ResponseConstraint (Schema d) a = ()
231 type ResponseArgs (Schema d) a = SchemaResponseArgs a
232 type Response (Schema d) = ()
233 response' = Schema $ \_inh -> Nothing
235 -- ** Type 'SchemaPerm'
236 data SchemaPerm d k a = SchemaPerm
237 { schemaPerm_finalizer :: forall b c. Schema d (b->c) c -> Schema d (b->c) c
238 -- ^ Used to implement 'rule'.
239 , schemaPerm_alternatives :: [Schema d (a->k) k]
240 -- ^ Collect alternatives for rendering them all at once in 'runPermutation'.
242 instance Functor (SchemaPerm d k) where
243 _f`fmap`SchemaPerm w ps = SchemaPerm w (coerceSchema <$> ps)
244 instance Applicative (SchemaPerm d k) where
245 pure _a = SchemaPerm id mempty
246 SchemaPerm fd w <*> SchemaPerm fx x =
247 SchemaPerm (fd . fx) $ (coerceSchema <$> w) <> (coerceSchema <$> x)
248 instance Docable d => Permutable (Schema d) where
249 type Permutation (Schema d) = SchemaPerm d
250 runPermutation (SchemaPerm w ps) =
252 [] -> w $ Schema $ \_inh -> Nothing
253 [Schema s] -> w $ Schema s
254 _ -> w $ Schema $ \inh -> Just $
255 -- pairIfNeeded (schemaInh_op inh) op $
257 Doc.intercalate Doc.breakspace $
258 catMaybes $ (<$> ps) $ \(Schema s) ->
260 { schemaInh_op=(op, SideL)
261 , schemaInh_or=docOrH }
263 toPermutation = SchemaPerm id . pure
264 toPermDefault _a s = SchemaPerm id $ pure $ Schema $ \inh -> Just $
265 if needsParenInfix (schemaInh_op inh) op
269 -- Doc.withBreakable Nothing $
270 runSchema s inh{schemaInh_op=(op, SideL), schemaInh_or=docOrH})
274 runSchema s inh{schemaInh_op=(op, SideL)} <>
277 runSchema s inh{schemaInh_op=(op, SideL)}
279 instance Docable d => CLI_Help (SchemaPerm d) where
280 type HelpConstraint (SchemaPerm d) d' = d ~ d'
282 program n (SchemaPerm w ps) = SchemaPerm (program n . w) ps
283 rule n (SchemaPerm w ps) = SchemaPerm (rule n . w) ps