Improve help rendition.
authorJulien Moutinho <julm+symantic@autogeree.net>
Tue, 10 Apr 2018 03:09:33 +0000 (05:09 +0200)
committerJulien Moutinho <julm+symantic@autogeree.net>
Mon, 4 Jun 2018 12:31:20 +0000 (14:31 +0200)
symantic-cli/Language/Symantic/CLI/HLint.hs [new symlink]
symantic-cli/Language/Symantic/CLI/Help.hs [moved from symantic-cli/Language/Symantic/CLI/Write/Help.hs with 57% similarity]
symantic-cli/Language/Symantic/CLI/Plain.hs [moved from symantic-cli/Language/Symantic/CLI/Write/Plain.hs with 92% similarity]
symantic-cli/Language/Symantic/CLI/Read.hs
symantic-cli/Language/Symantic/CLI/Sym.hs
symantic-cli/stack.yaml
symantic-cli/symantic-cli.cabal

diff --git a/symantic-cli/Language/Symantic/CLI/HLint.hs b/symantic-cli/Language/Symantic/CLI/HLint.hs
new file mode 120000 (symlink)
index 0000000..ab18269
--- /dev/null
@@ -0,0 +1 @@
+../HLint.hs
\ No newline at end of file
similarity index 57%
rename from symantic-cli/Language/Symantic/CLI/Write/Help.hs
rename to symantic-cli/Language/Symantic/CLI/Help.hs
index 984b8edf3ddbaedd15c423160575d431c204d7df..778b8a9764044b13f7b5ac5958cddf42c42a54a6 100644 (file)
@@ -1,25 +1,24 @@
 {-# LANGUAGE OverloadedStrings #-}
-module Language.Symantic.CLI.Write.Help where
+module Language.Symantic.CLI.Help where
 
 import Control.Applicative (Applicative(..))
 import Data.Bool
-import Data.Function (($))
+import Data.Function (($), (.))
 import Data.Functor ((<$>))
 import Data.Functor.Compose (Compose(..))
-import Data.Maybe (Maybe(..))
+import Data.Maybe (Maybe(..), maybeToList, maybe)
 import Data.Monoid (Monoid(..))
 import Data.Semigroup (Semigroup(..))
 import qualified Language.Symantic.Document.Term as Doc
+import Data.Tree as Tree
 
 import Language.Symantic.CLI.Sym
-import qualified Language.Symantic.CLI.Write.Plain as Plain
+import qualified Language.Symantic.CLI.Plain as Plain
 
 -- * Type 'Reader'
 data Reader d
  =   Reader
- {   reader_help           :: Maybe d -- ^ Current help.
- -- ,   reader_define         :: Bool    -- ^ Whether to print a definition, or not.
- -- ,   reader_or             :: d
+ {   reader_help           :: Maybe d
  ,   reader_command_indent :: Doc.Indent
  ,   reader_option_indent  :: Doc.Indent
  ,   reader_plain          :: Plain.Reader d
@@ -29,20 +28,57 @@ data Reader d
 defReader :: Doc.Textable d => Reader d
 defReader = Reader
  { reader_help           = Nothing
- -- , reader_define         = True
- -- , reader_or             = Doc.stringH " | "
  , reader_command_indent = 2
- , reader_option_indent  = 20
+ , reader_option_indent  = 15
  , reader_plain          = Plain.defReader
  , reader_option_empty   = False
  }
 
 -- * Type 'Result'
-type Result d = [d]
+type Result d = Tree.Forest (DocNode d)
 
 defResult :: Monoid d => Result d
 defResult = mempty
 
+-- ** Type 'DocNode'
+data DocNode d
+ =   Leaf
+     { docNodeSep    :: d
+     , docNode       :: d
+     }
+ |   Indented
+     { docNodeIndent :: Doc.Indent
+     , docNodeSep    :: d
+     , docNode       :: d
+     }
+ |   BreakableFill
+     { docNodeIndent :: Doc.Indent
+     , docNodeSep    :: d
+     , docNode       :: d
+     }
+
+docTree ::
+ Monoid d =>
+ Doc.Textable d =>
+ Doc.Indentable d =>
+ Tree (DocNode d) -> d
+docTree (Tree.Node n []) = docNode n
+docTree (Tree.Node n ts) =
+       case n of
+        Leaf{} -> docNode n
+        Indented      ind _sep d -> d <> Doc.incrIndent ind (Doc.newline <> docTrees ts)
+        BreakableFill ind _sep d -> Doc.breakableFill ind d <> (Doc.align $ docTrees ts)
+
+docTrees ::
+ Monoid d =>
+ Doc.Textable d =>
+ Doc.Indentable d =>
+ Tree.Forest (DocNode d) -> d
+docTrees [] = Doc.empty
+docTrees [t] = docTree t
+docTrees (t0:ts) =
+       docTree t0 <> mconcat ((\t@(Tree.Node n _ns) -> docNodeSep n <> docTree t) <$> ts)
+
 -- * Type 'Help'
 data Help d e t a
  =   Help
@@ -50,13 +86,11 @@ data Help d e t a
  ,   help_plain  :: Plain.Plain d e t a
  }
 
-runHelp :: Monoid d => Doc.Textable d => Help d e t a -> d
-runHelp h = Doc.catV $ help_result h defReader
+runHelp :: Monoid d => Doc.Indentable d => Doc.Textable d => Help d e t a -> d
+runHelp h = docTrees $ help_result h defReader
 
 textHelp :: Plain.Doc d => Reader d -> Help d e t a -> d
-textHelp def (Help h _p) =
-       let res = h def in
-       Doc.catV res
+textHelp def (Help h _p) = docTrees $ h def
 
 coerceHelp :: Help d e s a -> Help d e t b
 coerceHelp Help{help_plain, ..} = Help
@@ -107,29 +141,40 @@ instance Plain.Doc d => Sym_Rule (Help d) where
        rule n (Help h p) =
                Help (\ro ->
                        pure $
-                       Doc.breakableFill 4 (ref<>" ::= "<> Plain.runPlain p' (reader_plain ro)) <>
-                       Doc.align (Doc.catV (h ro{reader_help=Nothing}))
+                       Tree.Node (Indented
+                        (reader_command_indent ro)
+                        (Doc.newline <> Doc.newline) $
+                               ref<>" ::= "<>Plain.runPlain p' (reader_plain ro)) $
+                       maybeToList (pure . Leaf Doc.empty <$> reader_help ro) <>
+                       h ro{reader_help=Nothing}
                 ) p'
                where
                p' = rule n p
-               ref = Doc.bold $ Doc.between (Doc.charH '<') (Doc.charH '>') (Doc.textH n)
+               ref =
+                       Doc.bold $
+                       Doc.between (Doc.charH '<') (Doc.charH '>') $
+                       Doc.magentaer $
+                       Doc.stringH n
 instance Plain.Doc d => Sym_Option (Help d) where
-       var n (Help _h p) = Help mempty (var n p)
-       string = Help mempty string
+       var n f = Help mempty (var n f)
        tag n = Help mempty (tag n)
        opt n (Help _h p) =
                Help (\ro ->
                        case reader_help ro of
                         Nothing ->
                                if reader_option_empty ro
-                               then pure $ Doc.bold (Plain.runPlain p' (reader_plain ro))
+                               then
+                                       pure $ pure $ Leaf Doc.newline $ Doc.bold $
+                                       Plain.runPlain p' (reader_plain ro)
                                else []
                         Just msg ->
                                pure $
-                               Doc.breakableFill (reader_option_indent ro)
-                                (Doc.bold $ Plain.runPlain p' (reader_plain ro) <>
-                                       Doc.spaces 2) <>
-                               Doc.align msg
+                               Tree.Node
+                                (BreakableFill
+                                        (reader_option_indent ro)
+                                        Doc.newline
+                                        (Doc.bold $ Plain.runPlain p' (reader_plain ro) <> Doc.space)) $
+                               pure $ pure $ Leaf Doc.empty msg
                 ) p'
                where p' = opt n p
 instance Plain.Doc d => Sym_Help d (Help d) where
@@ -139,28 +184,40 @@ instance Plain.Doc d => Sym_Help d (Help d) where
 instance Plain.Doc d => Sym_Command (Help d) where
        main n (Help h p) =
                Help (\ro ->
-                       [ Plain.runPlain p' (reader_plain ro) <>
-                               (case reader_help ro of
-                                Nothing -> Doc.empty
-                                Just msg ->
-                                       Doc.incrIndent (reader_command_indent ro) $
-                                       Doc.newline <> msg)
-                       , Doc.catV $ h ro{reader_help=Nothing}
-                       ]
+                       pure $
+                       Tree.Node (Indented 0
+                                (Doc.newline <> Doc.newline) $
+                                Plain.runPlain p' (reader_plain ro) <>
+                                       maybe Doc.empty
+                                        (\d -> Doc.newline <> Doc.newline <> d <> Doc.newline)
+                                        (reader_help ro)
+                        ) $
+                       h ro{reader_help=Nothing}
                 ) p'
                where p' = main n p
        command n (Help h p) =
                Help (\ro ->
                        pure $
-                       let d = ref<>" ::= "<>Plain.runPlain p' (reader_plain ro) in
-                       case h ro{reader_help=Nothing} of
-                        [] -> d
-                        hs ->
-                               Doc.breakableFill 4 d <>
-                               Doc.align (Doc.catV hs)
+                       Tree.Node
+                        (Indented
+                                (reader_command_indent ro)
+                                (Doc.newline <> Doc.newline) $
+                                       ref<>" ::= " <>
+                                       Plain.runPlain p' (reader_plain ro) <>
+                                       maybe Doc.empty
+                                        ( (<> Doc.newline)
+                                        . Doc.incrIndent (reader_command_indent ro)
+                                        . (Doc.newline <>) )
+                                        (reader_help ro)
+                        ) $
+                       h ro{reader_help=Nothing}
                 ) p'
                where
                p' = command n p
-               ref = Doc.bold $ Doc.between (Doc.charH '<') (Doc.charH '>') (Doc.stringH n)
+               ref =
+                       Doc.bold $
+                       Doc.between (Doc.charH '<') (Doc.charH '>') $
+                       Doc.magentaer $
+                       Doc.stringH n
 instance Plain.Doc d => Sym_Exit (Help d) where
        exit e = Help mempty $ exit e
similarity index 92%
rename from symantic-cli/Language/Symantic/CLI/Write/Plain.hs
rename to symantic-cli/Language/Symantic/CLI/Plain.hs
index fc97c110920418880baec18bc847c722a9e13d9b..981c251bf666c912a278ee1b2829763a2f9f52b4 100644 (file)
@@ -1,5 +1,5 @@
 {-# LANGUAGE OverloadedStrings #-}
-module Language.Symantic.CLI.Write.Plain where
+module Language.Symantic.CLI.Plain where
 
 import Data.Bool
 import Data.Function (($), (.))
@@ -40,21 +40,15 @@ words m = Doc.breakableSpaces $ Doc.string <$> Doc.words m
 data Reader d
  =   Reader
  {   reader_op             :: (Infix, Side) -- ^ Parent operator.
- -- ,   reader_help           :: Maybe d       -- ^ Current help.
  ,   reader_define         :: Bool          -- ^ Whether to print a definition, or not.
  ,   reader_or             :: d
- -- ,   reader_command_indent :: Doc.Indent
- -- ,   reader_option_indent  :: Doc.Indent
  }
 
 defReader :: Doc.Textable d => Reader d
 defReader = Reader
  { reader_op             = (infixN0, SideL)
- -- , reader_help           = Nothing
  , reader_define         = True
  , reader_or             = Doc.stringH " | "
- -- , reader_command_indent = 2
- -- , reader_option_indent  = 20
  }
 
 pairIfNeeded :: Doc d => Reader d -> Infix -> d -> d
@@ -184,10 +178,13 @@ instance Doc d => Sym_Rule (Plain d) where
                then runPlain p ro{reader_define=False}
                else ref
                where
-               ref = Doc.bold $ Doc.between (Doc.charH '<') (Doc.charH '>') (Doc.textH n)
+               ref =
+                       Doc.bold $
+                       Doc.between (Doc.charH '<') (Doc.charH '>') $
+                       Doc.magentaer $
+                       Doc.stringH n
 instance Doc d => Sym_Option (Plain d) where
-       var n _r = fromString $ "<"<>n<>">"
-       string = fromString "<string>"
+       var n _f = Plain $ \_ro -> Just $ Doc.between (Doc.charH '<') (Doc.charH '>') (Doc.string n)
        tag = fromString
        opt n r = Plain $ \ro ->
                unPlain (prefix n <**> coercePlain r) ro
@@ -205,7 +202,11 @@ instance Doc d => Sym_Command (Plain d) where
                         ro{reader_define = False}
                else ref
                where
-               ref = Doc.bold $ Doc.between (Doc.charH '<') (Doc.charH '>') (Doc.stringH n)
+               ref =
+                       Doc.bold $
+                       Doc.between (Doc.charH '<') (Doc.charH '>') $
+                       Doc.magentaer $
+                       Doc.stringH n
        command = main
 instance Doc d => Sym_Help d (Plain d) where
        help _msg p = p
index e8eb521bfccdec8687e0dc8ac4baa4deb2d46344..e3c15d9f076653da75cab4c92672a015c3adabc9 100644 (file)
@@ -4,10 +4,6 @@
 {-# LANGUAGE ScopedTypeVariables #-}
 module Language.Symantic.CLI.Read where
 
--- import Control.Monad.Trans.Class (MonadTrans(..))
--- import Data.Char (Char)
--- import Data.Default.Class (Default(..))
--- import qualified Control.Monad.Trans.State as S
 import Control.Applicative (Applicative(..), Alternative(..))
 import Control.Arrow ((***))
 import Control.Monad (Monad(..), MonadPlus(..))
@@ -23,7 +19,6 @@ import Data.Ord (Ord(..), Ordering(..))
 import Data.Semigroup (Semigroup(..))
 import Data.String (String)
 import Text.Show (Show(..))
-import qualified Control.Monad.Trans.Reader as R
 import qualified Data.List as List
 import qualified Data.Set as Set
 import qualified Text.Megaparsec as P
@@ -64,20 +59,9 @@ instance P.ShowToken Arg where
                showArg (Arg a@('-':_)) = a
                showArg (Arg a) = "\""<>a<>"\""
 
--- * Type 'Reader'
-newtype Reader
- =      Reader
- {      reader_var :: Name
- }
-
-defReader :: Reader
-defReader = Reader
- { reader_var = ""
- }
-
 -- * Type 'Parser'
 newtype Parser e s a
- =      Parser { unParser :: R.ReaderT Reader (P.Parsec (ErrorRead e) Args) a }
+ =      Parser { unParser :: P.Parsec (ErrorRead e) Args a }
  deriving (Functor, Applicative, Alternative, Monad, MonadPlus, P.MonadParsec (ErrorRead e) Args)
 
 coerceParser :: Parser e s a -> Parser e t a
@@ -117,13 +101,14 @@ instance Sym_Command Parser where
                 ( Just $ P.Tokens $ pure t
                 , Set.singleton $ P.Tokens $ pure expected )
 instance Sym_Option Parser where
-       var n (Parser p) = Parser $ R.local (\ro -> ro{reader_var = n}) p
-       string = do
-               name <- Parser $ R.asks reader_var
+       var n f = do
                let check = Right
-               let expected | List.null name = Arg "<string>"
-                            | otherwise      = Arg $ "<"<>name<>">"
-               unArg <$> P.token check (Just expected)
+               let expected | List.null n = Arg "<string>"
+                            | otherwise   = Arg $ "<"<>n<>">"
+               Arg arg <- P.token check (Just expected)
+               case f arg of
+                Right a -> return a
+                Left err -> P.customFailure $ ErrorRead err
        tag n = do
                let expected = Arg n
                let check t | t == expected = Right ()
@@ -164,7 +149,9 @@ instance Sym_Exit Parser where
 -- * Type 'ErrorRead'
 newtype ErrorRead e
  =      ErrorRead e
- deriving (Functor, Show)
+ deriving (Functor)
+instance Show e => Show (ErrorRead e) where
+       showsPrec p (ErrorRead e) = showsPrec p e
 instance Eq (ErrorRead a) where
        _==_ = True
 instance Ord (ErrorRead a) where
@@ -173,4 +160,4 @@ instance Show e => P.ShowErrorComponent (ErrorRead e) where
        showErrorComponent = show
 
 readArgs :: Parser e s a -> Args -> Either (P.ParseError (P.Token Args) (ErrorRead e)) a
-readArgs p = P.runParser ((`R.runReaderT` defReader) $ unParser $ p <* end) ""
+readArgs p = P.runParser (unParser $ p <* end) ""
index 914a361bfdb376745feb53c18d89bd33c9d418bf..dab3b58a9ba01aeee6905595c62fa516e821e7cf 100644 (file)
@@ -3,13 +3,13 @@ module Language.Symantic.CLI.Sym where
 
 import Data.Bool
 import Data.Char (Char)
+import Data.Either (Either(..))
 import Data.Eq (Eq)
 import Data.Function (($), (.), const, id)
 import Data.Functor ((<$>))
 import Data.Maybe (Maybe(..), catMaybes)
 import Data.Ord (Ord(..))
 import Data.String (String)
-import Data.Text (Text)
 import Text.Show (Show)
 
 -- * @Arg@ types
@@ -64,7 +64,7 @@ class Sym_Interleaved repr where
        (<<|?>>) :: Perm (repr e t) (a -> b) -> (a, repr e t a) -> Perm (repr e t) b
        (<<|*>>) :: Perm (repr e t) ([a] -> b) -> repr e t a -> Perm (repr e t) b
        
-       (<<$)    :: a -> repr e t b -> Perm (repr e t) a
+       (<<$) :: a -> repr e t b -> Perm (repr e t) a
        (<<$) = (<<$>>) . const
        (<<$?) :: a -> (b, repr e t b) -> Perm (repr e t) a
        a <<$? b = const a <<$?>> b
@@ -84,7 +84,7 @@ infixl 1 <<|>>, <<|?>>, <<|*>>
 type family Perm (repr:: * -> *) = (r :: * -> *) | r -> repr
 -- * Class 'Sym_Rule'
 class Sym_Rule repr where
-       rule :: {-Show a =>-} Text -> repr e t a -> repr e t{-(ArgRule t)-} a
+       rule :: String -> repr e t a -> repr e t a
        -- rule _n = id
 -- * Class 'Sym_Command'
 class Sym_Command repr where
@@ -93,8 +93,7 @@ class Sym_Command repr where
 -- * Class 'Sym_Option'
 class Sym_AltApp repr => Sym_Option repr where
        opt    :: OptionName -> repr e s a -> repr e ArgOption a
-       var    :: Name -> repr e ArgValue a -> repr e ArgValue a
-       string :: repr e ArgValue String
+       var    :: Name -> (String -> Either e a) -> repr e ArgValue a
        tag    :: String -> repr e ArgValue ()
        -- int    :: repr e ArgValue Int
        
@@ -102,10 +101,12 @@ class Sym_AltApp repr => Sym_Option repr where
        short  :: Char -> repr e ArgValue a -> repr e ArgOption a
        flag   :: OptionName -> (Bool, repr e ArgOption Bool)
        endOpt :: repr e ArgOption ()
+       string :: Name -> repr e ArgValue String
        long   = opt . OptionNameLong
        short  = opt . OptionNameShort
        flag n = (False,) $ opt n $ value True
        endOpt = option () $ opt (OptionNameLong "") $ value ()
+       string n = var n Right
 -- ** Type 'OptionName'
 data OptionName
  =   OptionName Char Name
index 28ab1e01409e6563faae6d399d4cab7b3233092d..3066b2c4b245df23c11a7ba6f1ab640b713db97b 100644 (file)
@@ -1,8 +1,6 @@
 resolver: lts-10.5
 packages:
 - '.'
-- location: '../localization'
-  extra-dep: true
 - location: '../symantic-document'
   extra-dep: true
 
index 4450d7c58337e3edf1d770facbd8baa05b188919..61212dd2dbd15a426626074c5604ffb32c8256e4 100644 (file)
@@ -2,7 +2,7 @@ name: symantic-cli
 -- PVP:  +-+------- breaking API changes
 --       | | +----- non-breaking API additions
 --       | | | +--- code changes with no API change
-version: 0.0.0.20180401
+version: 0.0.0.20180410
 category: Data Structures
 synopsis: Library for Command Line Interface (CLI)
 description: Symantics for CLI.
@@ -30,11 +30,10 @@ Library
   exposed-modules:
     Language.Symantic.CLI
     Language.Symantic.CLI.Fixity
+    Language.Symantic.CLI.Help
+    Language.Symantic.CLI.Plain
     Language.Symantic.CLI.Read
     Language.Symantic.CLI.Sym
-    Language.Symantic.CLI.Write.Help
-    Language.Symantic.CLI.Write.Plain
-    -- Language.Symantic.CLI.Test
   default-language: Haskell2010
   default-extensions:
     FlexibleContexts