cli: rewrite using new symantic-cli
authorJulien Moutinho <julm+hdoc@autogeree.net>
Thu, 5 Mar 2020 02:04:13 +0000 (03:04 +0100)
committerJulien Moutinho <julm+hdoc@autogeree.net>
Thu, 5 Mar 2020 02:30:27 +0000 (03:30 +0100)
cli/HLint.hs [new symlink]
cli/Main.hs [new file with mode: 0644]
cli/Textphile/CLI.hs [new file with mode: 0644]
cli/Textphile/CLI/Compile.hs [new file with mode: 0644]
cli/Textphile/CLI/HLint.hs [new symlink]
cli/Textphile/CLI/Lang.hs [new file with mode: 0644]
cli/Textphile/CLI/Source.hs [new file with mode: 0644]
cli/Textphile/CLI/Utils.hs [new file with mode: 0644]
cli/Textphile/HLint.hs [new symlink]
src/Textphile/DTC/Write/HTML5.hs
textphile.cabal

diff --git a/cli/HLint.hs b/cli/HLint.hs
new file mode 120000 (symlink)
index 0000000..ab18269
--- /dev/null
@@ -0,0 +1 @@
+../HLint.hs
\ No newline at end of file
diff --git a/cli/Main.hs b/cli/Main.hs
new file mode 100644 (file)
index 0000000..ad43a6d
--- /dev/null
@@ -0,0 +1,14 @@
+module Main where
+
+import Data.Function (($))
+import System.IO (IO)
+import qualified Symantic.CLI as CLI
+import qualified System.Environment as Env
+
+import Textphile.CLI
+
+main :: IO ()
+main = do
+       args <- Env.getArgs
+       -- putStrLn $ "args: " <> show args
+       parseAPI $ CLI.lexer args
diff --git a/cli/Textphile/CLI.hs b/cli/Textphile/CLI.hs
new file mode 100644 (file)
index 0000000..9ff865c
--- /dev/null
@@ -0,0 +1,62 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE NoMonomorphismRestriction #-}
+{-# LANGUAGE TypeApplications #-}
+{-# LANGUAGE NamedFieldPuns #-}
+{-# OPTIONS_GHC -Wno-missing-signatures #-}
+module Textphile.CLI where
+
+import Control.Monad (Monad(..))
+import Data.Bool
+import Data.Function (($))
+import Data.Semigroup (Semigroup(..))
+import Data.Text (Text)
+import Data.Void (Void)
+import Symantic.CLI as CLI
+import qualified Data.Text.Lazy.IO as TL
+import qualified Symantic.Document as Doc
+import qualified Symantic.RNC.Write as RNC
+import qualified System.IO as IO
+import qualified Textphile.DTC.Sym as DTC
+
+import Textphile.CLI.Lang
+import Textphile.CLI.Utils
+import Textphile.CLI.Compile
+import Textphile.CLI.Source
+
+parseAPI = parser @Void @Doc api run
+
+api =
+       program "textphile" $
+               api_commands <!>
+               api_help True <!>
+               api_version
+run =
+       run_commands :!:
+       run_help api :!:
+       run_version
+
+api_commands =
+       (api_options <?>) $
+       api_command_compile <!>
+       api_command_source <!>
+       api_command_schema
+run_commands opts =
+       run_command_compile opts :!:
+       run_command_source opts :!:
+       run_command_schema
+
+api_version =
+       helps l10n_help_version $
+       tag (TagLong "version") nothing
+       <.> response @Text
+run_version =
+       return (Doc.from version <> Doc.newline)
+
+api_command_schema =
+       helps l10n_help_command_schema $
+       command "schema" $
+               response @()
+run_command_schema =
+       TL.hPutStrLn IO.stdout $
+               RNC.writeRNC DTC.schema DTC.schema
diff --git a/cli/Textphile/CLI/Compile.hs b/cli/Textphile/CLI/Compile.hs
new file mode 100644 (file)
index 0000000..4943771
--- /dev/null
@@ -0,0 +1,189 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE StrictData #-}
+{-# OPTIONS_GHC -Wno-missing-signatures #-}
+module Textphile.CLI.Compile where
+
+import Control.Applicative (Applicative(..), Alternative(..))
+import Control.Monad (Monad(..), forM_, unless, when)
+import Control.Monad.Trans.Except (runExcept)
+import Control.Monad.Trans.Maybe (MaybeT(..))
+import Control.Monad.Trans.State.Strict (runState)
+import Data.Bool
+import Data.Either (Either(..))
+import Data.Eq (Eq(..))
+import Data.Foldable as Foldable (Foldable(..))
+import Data.Function (($), (.), id, flip)
+import Data.Functor ((<$>))
+import Data.Maybe (Maybe(..), fromMaybe)
+import Data.Ord (Ord(..))
+import Data.Proxy (Proxy(..))
+import Data.Semigroup (Semigroup(..))
+import Data.Text (Text)
+import Prelude (error)
+import Symantic.CLI as CLI
+import System.FilePath ((-<.>))
+import System.IO (IO, FilePath)
+import Text.Show (Show(..))
+import qualified Data.ByteString as BS
+import qualified Data.List as List
+import qualified Data.Text as Text
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.IO as TL
+import qualified Data.TreeSeq.Strict as Tree
+import qualified Paths_textphile as Textphile
+import qualified Symantic.Document as Doc
+import qualified System.Directory as IO
+import qualified System.Environment as Env
+import qualified System.FilePath as FP
+import qualified System.FilePath as FilePath
+import qualified System.IO as IO
+import qualified Text.Blaze.DTC as Blaze.DTC
+import qualified Text.Blaze.HTML5 as Blaze.HTML5
+import qualified Text.Blaze.Renderer.Utf8 as Blaze
+import qualified Text.Blaze.Utils         as Blaze
+import qualified Text.Megaparsec as P
+import qualified Textphile.DTC.Read.TCT    as DTC
+import qualified Textphile.DTC.Sym         as DTC
+import qualified Textphile.DTC.Write.HTML5 as DTC
+import qualified Textphile.DTC.Write.XML   as DTC
+import qualified Textphile.TCT             as TCT
+import qualified Textphile.TCT.Write.HTML5 as TCT
+import qualified Textphile.TCT.Write.Plain as TCT
+import qualified Textphile.TCT.Write.XML   as TCT
+import qualified Textphile.Utils           as FS
+
+import Textphile.CLI.Lang
+import Textphile.CLI.Utils
+
+data Cfg_Compile = Cfg_Compile
+ {   cfg_compile_dump_tct  :: Bool
+ ,   cfg_compile_dump_xml  :: Bool
+ ,   cfg_compile_dump_deps :: Bool
+ } deriving (Show)
+
+data Cfg_Compile_HTML5
+ =   Cfg_Compile_HTML5
+ {   cfg_compile_html5_output_css :: Maybe FP.FilePath
+ ,   cfg_compile_html5_output_js  :: Maybe FP.FilePath
+ ,   cfg_compile_html5_dump_dtc   :: Bool
+ }
+
+api_command_compile =
+       helps l10n_help_command_compile $
+       command "compile" $
+               (Cfg_Compile
+                <$> api_dump_tct
+                <*> api_dump_xml
+                <*> api_dump_deps
+               )
+               <?> api_format
+               -- <!> api_help False
+       where
+       api_dump_tct =
+               flag "dump-tct"
+       api_dump_xml =
+               flag "dump-xml"
+       api_dump_deps =
+               flag "dump-deps"
+       api_format =
+               api_format_html5 <!>
+               api_format_xml
+       api_output_css =
+               helps l10n_help_opt_output_css $
+               optionalTag "output-css" $
+                       var "FILE"
+       api_output_js =
+               helps l10n_help_opt_output_js $
+               optionalTag "output-js" $
+                       var "FILE"
+       api_dump_dtc =
+               flag "dump-dtc"
+       api_format_html5 =
+               command "html5" $
+                       (Cfg_Compile_HTML5
+                        <$> api_output_css
+                        <*> api_output_js
+                        <*> api_dump_dtc
+                       )
+                       <?> api_input
+                       <.> response @()
+       api_format_xml =
+               command "xml" $
+                       api_input
+                       <.> response @()
+       api_input =
+               helps l10n_help_opt_input $
+               var @FP.FilePath "INPUT"
+
+run_command_compile
+ cfg_global@Cfg_Global{..}
+ cfg_compile@Cfg_Compile{..} =
+       run_compile_html5 :!:
+       run_compile_xml
+       where
+       run_compile_dtc cfg_compile_input cfg_compile_output = do
+               outputInfo cfg_global $ "compiling... " <> Doc.from cfg_compile_input
+               TCT.readTCT cfg_compile_input >>= \case
+                Left err -> error $ show err
+                Right tct -> do
+                       when cfg_compile_dump_tct $ do
+                               FS.writeFile (cfg_compile_output-<.>"tct.dump") $
+                                       TL.pack $ Tree.prettyTrees tct
+                       let xml = TCT.writeXML tct
+                       when cfg_compile_dump_xml $ do
+                               FS.writeFile (cfg_compile_output-<.>"xml.dump") $
+                                       TL.pack $ Tree.prettyTrees xml
+                       case DTC.readDTC xml of
+                        Left err -> do
+                               FS.removeFile $ cfg_compile_output-<.>"deps"
+                               error $ List.unlines $ Foldable.toList $ P.parseErrorPretty <$> P.bundleErrors err
+                        Right dtc -> do
+                               when cfg_compile_dump_deps $ do
+                                       writeDependencies cfg_compile cfg_compile_input tct
+                               when cfg_compile_dump_xml $ do
+                                       FS.writeFile (cfg_compile_output-<.>"dtc.dump") $
+                                               TL.pack $ show dtc
+                               return dtc
+       run_compile_html5 Cfg_Compile_HTML5{..} cfg_compile_input = do
+               let cfg_compile_output = cfg_compile_input-<.>"html5"
+               dtc <- run_compile_dtc cfg_compile_input cfg_compile_output
+               config_css <- installFile cfg_compile_html5_output_css $ "style" FilePath.</>"dtc-html5.css"
+               config_js  <- installFile cfg_compile_html5_output_js  $ "style" FilePath.</>"dtc-html5.js"
+               let conf = DTC.Config
+                        { DTC.config_css
+                        , DTC.config_js
+                        , DTC.config_locale    = cfg_global_lang
+                        , DTC.config_generator = TL.fromStrict version
+                        }
+               FS.withFile cfg_compile_output IO.WriteMode $ \h -> do
+                       html <- DTC.writeHTML5 conf dtc
+                       Blaze.prettyMarkupIO
+                        Blaze.HTML5.isInlinedElement
+                        (BS.hPutStr h)
+                        html
+               where
+               installFile out name = do
+                       dataDir <- Textphile.getDataDir
+                       let src = dataDir FilePath.</>name
+                       case out of
+                        Nothing -> Right <$> FS.readFile src
+                        Just "" -> return $ Left ""
+                        Just dst -> do
+                               IO.createDirectoryIfMissing True $ FilePath.takeDirectory dst
+                               IO.copyFile src dst
+                               return $ Left dst
+       run_compile_xml cfg_compile_input = do
+               let cfg_compile_output = cfg_compile_input-<.>"xml"
+               dtc <- run_compile_dtc cfg_compile_input cfg_compile_output
+               FS.withFile cfg_compile_output IO.WriteMode $ \h ->
+                       Blaze.prettyMarkupIO Blaze.DTC.isInlinedElement (BS.hPutStr h) $
+                       DTC.writeXML cfg_global_lang dtc
+
+writeDependencies :: Cfg_Compile -> FilePath -> TCT.Roots -> IO ()
+writeDependencies Cfg_Compile{..} cfg_compile_input tct =
+       let dir = FilePath.takeDirectory cfg_compile_input in
+       FS.writeFile (cfg_compile_input-<.>"deps") $
+               foldMap (TL.pack . (("\n" <>) . FilePath.normalise))
+                ((dir FilePath.</>) <$> TCT.dependencies tct) <>
+               "\n"
diff --git a/cli/Textphile/CLI/HLint.hs b/cli/Textphile/CLI/HLint.hs
new file mode 120000 (symlink)
index 0000000..ab18269
--- /dev/null
@@ -0,0 +1 @@
+../HLint.hs
\ No newline at end of file
diff --git a/cli/Textphile/CLI/Lang.hs b/cli/Textphile/CLI/Lang.hs
new file mode 100644 (file)
index 0000000..635311a
--- /dev/null
@@ -0,0 +1,354 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+module Textphile.CLI.Lang where
+
+import Control.Applicative (Applicative(..), (<*), liftA2, liftA3)
+import Control.Monad.Trans.Reader (Reader, runReader, ReaderT, runReaderT, ask)
+import Data.Bool
+import Data.Eq (Eq(..))
+import Data.Function (($), (.))
+import Data.Functor (Functor(..), (<$>))
+import Data.Locale
+import Data.Maybe (Maybe(..), fromMaybe, maybe)
+import Data.Monoid (Monoid(..))
+import Data.Semigroup (Semigroup(..))
+import Data.String (String, IsString(..))
+import Data.Text (Text)
+import Symantic.CLI (Outputable(..), OnHandle(..), IOType)
+import System.IO (IO, FilePath)
+import qualified Data.Char as Char
+import qualified Data.List as List
+import qualified Data.Map.Strict as Map
+import qualified Data.Text as Text
+import qualified Data.Text.Lazy.Builder as TLB
+import qualified Symantic.CLI as CLI
+import qualified Symantic.Document as Doc
+import qualified System.Environment as Env
+
+type Doc = Reader Loq (Doc.Plain TLB.Builder)
+
+instance (Semigroup d, Applicative m) => Semigroup (ReaderT r m (Doc.Plain d)) where
+       (<>) = liftA2 (<>)
+instance (Monoid d, Applicative m) => Monoid (ReaderT r m (Doc.Plain d)) where
+       mempty = pure mempty
+       mappend = liftA2 mappend
+instance
+ ( Doc.From (Doc.Word String) d
+ , Applicative m
+ , Doc.Spaceable d
+ ) => IsString (ReaderT r m (Doc.Plain d)) where
+       fromString = pure . fromString
+instance
+ ( Applicative m
+ , Doc.Spaceable d
+ ) => Doc.Spaceable (ReaderT r m (Doc.Plain d)) where
+       newline = pure Doc.newline
+       space = pure Doc.space
+instance
+ ( Functor m
+ , Semigroup d
+ , Doc.From [Doc.SGR] d
+ ) => Doc.Colorable16 (ReaderT r m (Doc.Plain d)) where
+       reverse     = fmap Doc.reverse
+       black       = fmap Doc.black
+       red         = fmap Doc.red
+       green       = fmap Doc.green
+       yellow      = fmap Doc.yellow
+       blue        = fmap Doc.blue
+       magenta     = fmap Doc.magenta
+       cyan        = fmap Doc.cyan
+       white       = fmap Doc.white
+       blacker     = fmap Doc.blacker
+       redder      = fmap Doc.redder
+       greener     = fmap Doc.greener
+       yellower    = fmap Doc.yellower
+       bluer       = fmap Doc.bluer
+       magentaer   = fmap Doc.magentaer
+       cyaner      = fmap Doc.cyaner
+       whiter      = fmap Doc.whiter
+       onBlack     = fmap Doc.onBlack
+       onRed       = fmap Doc.onRed
+       onGreen     = fmap Doc.onGreen
+       onYellow    = fmap Doc.onYellow
+       onBlue      = fmap Doc.onBlue
+       onMagenta   = fmap Doc.onMagenta
+       onCyan      = fmap Doc.onCyan
+       onWhite     = fmap Doc.onWhite
+       onBlacker   = fmap Doc.onBlacker
+       onRedder    = fmap Doc.onRedder
+       onGreener   = fmap Doc.onGreener
+       onYellower  = fmap Doc.onYellower
+       onBluer     = fmap Doc.onBluer
+       onMagentaer = fmap Doc.onMagentaer
+       onCyaner    = fmap Doc.onCyaner
+       onWhiter    = fmap Doc.onWhiter
+instance
+ ( Applicative m
+ , Semigroup d
+ , Doc.From [Doc.SGR] d
+ ) => Doc.Decorable (ReaderT r m (Doc.Plain d)) where
+       bold      = fmap Doc.bold
+       underline = fmap Doc.underline
+       italic    = fmap Doc.italic
+instance
+ ( Applicative m
+ , Semigroup d
+ , Doc.Spaceable d
+ ) => Doc.Wrappable (ReaderT r m (Doc.Plain d)) where
+       setWidth w = fmap (Doc.setWidth w)
+       breakpoint = pure Doc.breakpoint
+       breakspace = pure Doc.breakspace
+       breakalt   = liftA2 Doc.breakalt
+       endline    = pure Doc.endline
+instance
+ ( Applicative m
+ , Semigroup d
+ , Doc.Spaceable d
+ ) => Doc.Justifiable (ReaderT r m (Doc.Plain d)) where
+       justify = fmap Doc.justify
+instance
+ ( Applicative m
+ , Semigroup d
+ , Doc.Spaceable d
+ ) => Doc.Indentable (ReaderT r m (Doc.Plain d)) where
+       align = fmap Doc.align
+       setIndent d i x = Doc.setIndent <$> d <*> pure i <*> x
+       incrIndent d i x = Doc.incrIndent <$> d <*> pure i <*> x
+       fill i = fmap (Doc.fill i)
+       fillOrBreak i = fmap (Doc.fillOrBreak i)
+instance
+ ( Applicative m
+ , Semigroup d
+ , Doc.Lengthable s
+ , Doc.From (Doc.Word s) d
+ ) => Doc.From (Doc.Word s) (ReaderT r m (Doc.Plain d)) where
+       from = pure . Doc.from
+instance Doc.From String Doc where
+       from = pure . Doc.from
+instance Doc.From Text Doc where
+       from = pure . Doc.from
+
+instance IOType Doc
+instance Outputable Doc where
+       output d = do
+               lang <- getLang
+               output $ runReader d $ loqualize lang
+instance Outputable (OnHandle Doc) where
+       output (OnHandle h d) = do
+               lang <- getLang
+               output $ OnHandle h $ runReader d $ loqualize lang
+
+getLang :: IO Lang
+getLang =
+       (\v -> Map.findWithDefault
+        (LocaleIn @Langs en_US)
+        (Text.pack $ List.takeWhile (\c -> Char.isAlphaNum c || c == '_') v)
+        (locales @Langs)) .
+       fromMaybe ""
+        <$> Env.lookupEnv "LANG"
+
+type Name = Text
+
+-- * Type 'Lang'
+-- | Supported locales
+type Langs = '[FR, EN]
+type Lang  = LocaleIn Langs
+
+l10n_var v = "<"<>Doc.from v<>">"
+ref = Doc.underline
+con = Doc.between "\"" "\""
+fileRef = ref
+
+-- ** Class 'L10n'
+type Loq = Loqualization L10n
+
+helps ::
+ CLI.CLI_Help repr =>
+ CLI.HelpConstraint repr Doc =>
+ (forall lang. L10n lang => FullLocale lang -> Doc.Plain TLB.Builder) ->
+ repr f k -> repr f k
+helps f = CLI.help @_ @Doc (Doc.justify ((\(Loqualization fl) -> f fl) <$> ask))
+infixr 0 `helps`
+
+-- | Localization
+class L10n_Var lang => L10n lang where
+       l10n_cli                   :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_license               :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_version          :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_license          :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_command_source   :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_command_compile  :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_command_schema   :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_opt_lang         :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_opt_output       :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_opt_output_css   :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_opt_output_js    :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_opt_dump_tct     :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_opt_dump_xml     :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_opt_dump_deps    :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_opt_dump_dtc     :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_format           :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_format_plain     :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_format_html5     :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_format_xml       :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_opt_input        :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_opt_help_full    :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_opt_help_compact :: FullLocale lang -> Doc.Plain TLB.Builder
+       l10n_help_opt_verbosity    :: FullLocale lang -> Doc.Plain TLB.Builder
+
+instance L10n EN where
+       l10n_cli _ = "Compiler of Convivial Technical Document (DTC)."
+       l10n_license _ =
+               fromString $
+               List.intercalate "\n"
+                [ "License: GNU GPLv3+"
+                , "Copyright: Julien Moutinho <julm+textphile@sourcephile.fr>"
+                , ""
+                , "hdoc is free software: you can redistribute it and/or modify it"
+                , "under the terms of the GNU General Public License (GPL)"
+                , "as published by the Free Software Foundation;"
+                , "either in version 3, or (at your option) any later version."
+                , ""
+                , "hdoc is distributed in the hope that it will be useful,"
+                , "but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY"
+                , "or FITNESS FOR A PARTICULAR PURPOSE."
+                , ""
+                , "See the GNU GPL for more details."
+                , "You should have received a copy of the GNU GPL along with hdoc."
+                , "If not, see: http://www.gnu.org/licenses/"
+                ]
+       l10n_help_version         _ = "Show the version of this program."
+       l10n_help_license         _ = "Inform about the license of this program."
+       l10n_help_command_source  _ = "Format the source code of a TCT document."
+       l10n_help_command_compile _ = "Compile a TCT document into a format optimized for reading."
+       l10n_help_command_schema  _ = "Show in RNC (RelaxNG Compact) format the XML schema of the DTC format."
+       l10n_help_opt_lang l = mconcat
+        [ "Use the language given by "
+        , l10n_var $ l10n_var_locale l
+        , "."
+        ]
+       l10n_help_opt_output l = mconcat
+        [ "Output document into "
+        , l10n_var $ l10n_var_file l
+        ]
+       l10n_help_opt_output_css  l = mconcat
+        [ "Output CSS stylesheet into "
+        , l10n_var $ l10n_var_file l
+        , " (if any), instead of incorporating it into the HTML."
+        ]
+       l10n_help_opt_output_js l = mconcat
+        [ "Output JavaScript script into "
+        , l10n_var $ l10n_var_file l
+        , " (if any), instead of incorporating it into the HTML."
+        ]
+       l10n_help_opt_dump_tct l = mconcat
+        [ "Dump internal TCT representation of "
+        , l10n_var $ l10n_var_file l,".tct file,"
+        , " in a"
+        , l10n_var $ l10n_var_file l,".tct.dump file."
+        ]
+       l10n_help_opt_dump_xml l = mconcat
+        [ "Dump internal XML representation of "
+        , l10n_var $ l10n_var_file l,".tct file,"
+        , " in a"
+        , l10n_var $ l10n_var_file l,".xml.dump file."
+        ]
+       l10n_help_opt_dump_deps l = mconcat
+        [ "Dump dependencies of ", l10n_var $ l10n_var_file l,".tct file"
+        , " in ", l10n_var $ l10n_var_file l,".deps file,"
+        , " separated by newlines."
+        ]
+       l10n_help_opt_dump_dtc      _ = "Dump internal representation of DTC."
+       l10n_help_opt_help_full     _ = "Print a commented grammar tree to help using this program."
+       l10n_help_opt_help_compact  _ = "Print an uncommented grammar tree to help using this program."
+       l10n_help_format            _ = "Output format."
+       l10n_help_format_plain      _ = "Output as plain text."
+       l10n_help_format_html5      _ = "Output as HTML5."
+       l10n_help_format_xml        _ = "Output as XML."
+       l10n_help_opt_input l = mconcat
+        [ "Read input from ", l10n_var $ l10n_var_file l, "." ]
+       l10n_help_opt_verbosity _ =
+               "Verbosity level.\
+               \\n(default: "<>con "info"<>")"
+instance L10n FR where
+       l10n_cli _ = "Compilateur de Document Technique Convivial (DTC)."
+       l10n_license _ =
+               fromString $
+               List.intercalate "\n"
+                [ "Licence : GPLv3+ GNU"
+                , "Auteur : Julien Moutinho <julm+textphile@sourcephile.fr>"
+                , ""
+                , "textphile est un logiciel libre : vous pouvez le redistribuer et/ou le modifier"
+                , "selon les termes de la Licence Publique Générale (GPL) GNU"
+                , "telle que publiée par la Free Software Foundation ;"
+                , "en version 3, ou (à votre choix) n’importe quelle version ultérieure."
+                , ""
+                , "textphile est distribué dans l’espoir qu’il sera utile,"
+                , "mais SANS AUCUNE GARANTIE ; sans même la garantie implicite de COMMERCIALISATION"
+                , "ou de CONVENANCE À UN BUT PARTICULIER."
+                , ""
+                , "Voyez la GPL pour davantage de détails."
+                , "Vous devriez avoir reçu une copie de la GPL avec textphile."
+                , "Si non, voyez : http://www.gnu.org/licenses/"
+                ]
+       l10n_help_version         _ = "Affiche la version de ce logiciel."
+       l10n_help_license         _ = "Informe sur la licence de ce logiciel."
+       l10n_help_command_source  _ = "Lit un document TCT et écrit un rendu préservant sa syntaxe."
+       l10n_help_command_compile _ = "Compile un document TCT vers un format optimisé pour la lecture."
+       l10n_help_command_schema  _ = "Affiche au format RNC (RelaxNG Compact) le schéma XML du format DTC."
+       l10n_help_opt_lang l = mconcat
+        [ "Utilise le langage indiqué par "
+        , l10n_var $ l10n_var_locale l, "." ]
+       l10n_help_opt_output l = mconcat
+        [ "Écrit dans ", l10n_var $ l10n_var_file l, "." ]
+       l10n_help_opt_output_css l = mconcat
+        [ "Écrit la feuille de style CSS dans "
+        , l10n_var $ l10n_var_file l
+        , ", au lieu de l’incorporer dans le HTML."
+        ]
+       l10n_help_opt_output_js l = mconcat
+        [ "Écrit le script JavaScript dans "
+        , l10n_var $ l10n_var_file l
+        , ", au lieu de l’incorporer dans le HTML."
+        ]
+       l10n_help_opt_dump_tct l = mconcat
+        [ "Écrit la représentation TCT interne de "
+        , l10n_var $ l10n_var_file l,".tct,"
+        , " dans "
+        , l10n_var $ l10n_var_file l,".tct.dump."
+        ]
+       l10n_help_opt_dump_xml l = mconcat
+        [ "Écrit la représentation XML interne de "
+        , l10n_var $ l10n_var_file l,".tct,"
+        , " dans "
+        , l10n_var $ l10n_var_file l,".xml.dump."
+        ]
+       l10n_help_opt_dump_deps l = mconcat
+        [ "Écrit les dépendences de ", l10n_var $ l10n_var_file l,".tct"
+        , " dans ", l10n_var $ l10n_var_file l,".deps,"
+        , " séparées par des retours à la ligne."
+        ]
+       l10n_help_opt_dump_dtc     _ = "Écrit la représentation interne du DTC."
+       l10n_help_opt_help_full    _ = "Affiche un arbre grammatical avec commentaires pour aider à utiliser ce programme."
+       l10n_help_opt_help_compact _ = "Affiche un arbre grammatical sans commentaires pour aider à utiliser ce programme."
+       l10n_help_format           _ = "Format de sortie."
+       l10n_help_format_plain     _ = "Produit du texte brut."
+       l10n_help_format_html5     _ = "Produit du HTML5."
+       l10n_help_format_xml       _ = "Produit du XML."
+       l10n_help_opt_input l = mconcat
+        [ "Lit depuis ", l10n_var $ l10n_var_file l, "." ]
+       l10n_help_opt_verbosity _ =
+               "Niveau de verbosité.\
+               \\n(défault : "<>con "info"<>")"
+
+-- ** Class 'L10n_Var'
+class L10n_Var lang where
+       l10n_var_file   :: FullLocale lang -> Name
+       l10n_var_locale :: FullLocale lang -> Name
+instance L10n_Var EN where
+       l10n_var_file   _ = "file"
+       l10n_var_locale _ = "locale"
+instance L10n_Var FR where
+       l10n_var_file   _ = "fichier"
+       l10n_var_locale _ = "locale"
diff --git a/cli/Textphile/CLI/Source.hs b/cli/Textphile/CLI/Source.hs
new file mode 100644 (file)
index 0000000..5077946
--- /dev/null
@@ -0,0 +1,112 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE StrictData #-}
+{-# OPTIONS_GHC -Wno-missing-signatures #-}
+module Textphile.CLI.Source where
+
+import Control.Applicative (Applicative(..), Alternative(..))
+import Control.Monad (Monad(..), forM_, unless, when)
+import Control.Monad.Trans.Except (runExcept)
+import Control.Monad.Trans.Maybe (MaybeT(..))
+import Control.Monad.Trans.State.Strict (runState)
+import Data.Bool
+import Data.Either (Either(..))
+import Data.Eq (Eq(..))
+import Data.Foldable as Foldable (Foldable(..))
+import Data.Function (($), (.), id, flip)
+import Data.Functor ((<$>))
+import Data.Maybe (Maybe(..), fromMaybe)
+import Data.Ord (Ord(..))
+import Data.Proxy (Proxy(..))
+import Data.Semigroup (Semigroup(..))
+import Data.Text (Text)
+import Prelude (error)
+import Symantic.CLI as CLI
+import System.FilePath ((-<.>))
+import System.IO (IO, FilePath)
+import Text.Show (Show(..))
+import qualified Data.ByteString as BS
+import qualified Data.List as List
+import qualified Data.Text as Text
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.IO as TL
+import qualified Data.TreeSeq.Strict as Tree
+import qualified Paths_textphile as Textphile
+import qualified Symantic.Document as Doc
+import qualified System.Directory as IO
+import qualified System.Environment as Env
+import qualified System.FilePath as FP
+import qualified System.FilePath as FilePath
+import qualified System.IO as IO
+import qualified Text.Blaze.DTC           as Blaze.DTC
+import qualified Text.Blaze.HTML5         as Blaze.HTML5
+import qualified Text.Blaze.Renderer.Utf8 as Blaze
+import qualified Text.Blaze.Utils         as Blaze
+import qualified Text.Megaparsec as P
+import qualified Textphile.DTC.Read.TCT    as DTC
+import qualified Textphile.DTC.Sym         as DTC
+import qualified Textphile.DTC.Write.HTML5 as DTC
+import qualified Textphile.DTC.Write.XML   as DTC
+import qualified Textphile.TCT             as TCT
+import qualified Textphile.TCT.Write.HTML5 as TCT
+import qualified Textphile.TCT.Write.Plain as TCT
+import qualified Textphile.TCT.Write.XML   as TCT
+import qualified Textphile.Utils           as FS
+
+import Textphile.CLI.Lang
+import Textphile.CLI.Utils
+
+data Cfg_Source = Cfg_Source
+ {   cfg_source_dump_tct :: Bool
+ }
+
+api_command_source =
+       helps l10n_help_command_source $
+       command "source" $
+               (Cfg_Source
+                <$> api_dump_tct
+               )
+               <?> api_format
+       where
+       api_dump_tct =
+               flag "dump-tct"
+       api_format =
+               api_format_plain <!>
+               api_format_html5
+       api_format_plain =
+               command "plain" $
+                       api_input
+                       <.> response @()
+       api_format_html5 =
+               command "html5" $
+                       api_input
+                       <.> response @()
+       api_input =
+               helps l10n_help_opt_input $
+               var @FP.FilePath "INPUT"
+
+run_command_source
+ cfg_global@Cfg_Global{..}
+ cfg_source@Cfg_Source{..} =
+       run_source_plain :!:
+       run_source_html5
+       where
+       run_source_tct cfg_source_input cfg_source_output = do
+               TCT.readTCT cfg_source_input >>= \case
+                Left err -> error $ show err
+                Right tct -> do
+                       when cfg_source_dump_tct $
+                               FS.writeFile (cfg_source_output-<.>"tct.dump") $
+                                       TL.pack $ Tree.prettyTrees tct
+                       return tct
+       run_source_plain cfg_source_input = do
+               let cfg_source_output = cfg_source_input FilePath.<.>"txt"
+               tct <- run_source_tct cfg_source_input cfg_source_output
+               FS.writeFile cfg_source_output $
+                       TCT.writePlain tct
+       run_source_html5 cfg_source_input = do
+               let cfg_source_output = cfg_source_input FilePath.<.>"html5"
+               tct <- run_source_tct cfg_source_input cfg_source_output
+               FS.withFile cfg_source_output IO.WriteMode $ \h ->
+                       Blaze.renderMarkupToByteStringIO (BS.hPutStr h) $
+                               TCT.writeHTML5 tct
diff --git a/cli/Textphile/CLI/Utils.hs b/cli/Textphile/CLI/Utils.hs
new file mode 100644 (file)
index 0000000..2efb8ed
--- /dev/null
@@ -0,0 +1,160 @@
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE Rank2Types #-}
+{-# LANGUAGE StrictData #-}
+{-# OPTIONS_GHC -Wno-missing-signatures #-}
+{-# OPTIONS_GHC -Wno-orphans #-}
+module Textphile.CLI.Utils where
+
+import Control.Applicative (Applicative(..), Alternative(..))
+import Control.Arrow (left)
+import Control.Monad (Monad(..), forM_, when)
+import Control.Monad.Trans.Maybe (MaybeT(..))
+import Control.Monad.Trans.Except (runExceptT)
+import Control.Monad.Trans.Reader (Reader, runReader)
+import Data.Bool
+import Data.Either (Either(..))
+import Data.Eq (Eq(..))
+import Data.Foldable (Foldable)
+import Data.Function (($), (.), id)
+import Data.Functor ((<$>))
+import Data.Maybe (Maybe(..), maybe)
+import Data.Monoid (Monoid(..))
+import Data.Ord (Ord(..))
+import Data.Semigroup (Semigroup(..))
+import Data.Text (Text)
+import Data.Typeable (Typeable)
+import Prelude (min, max, (-))
+import Symantic.CLI as CLI
+import System.IO (IO)
+import Text.Show (Show(..))
+import qualified Control.Monad.Classes as MC
+import qualified Data.ByteString.Char8 as BS8
+import qualified Data.ByteString.Lazy.Char8 as BSL8
+import qualified Data.Char as Char
+import qualified Data.List as List
+import qualified Data.Map.Strict as Map
+import qualified Data.Text as Text
+import qualified Data.Text.Encoding as T
+import qualified Symantic.Document as Doc
+import qualified System.Console.Terminal.Size as Console
+import qualified System.Directory as IO
+import qualified System.FilePath as FP
+import qualified System.IO as IO
+import qualified System.Posix as Posix
+import qualified Data.Version as Version
+import qualified Paths_textphile as Textphile
+
+import Data.Locale
+import Textphile.CLI.Lang
+
+progname :: Text
+progname = "textphile"
+
+version :: Text
+version =
+       progname <> "-" <>
+       Text.pack (Version.showVersion Textphile.version)
+
+api_help full =
+       if full
+       then
+               api_compact <.> response @Doc <!>
+               api_full    <.> response @Doc
+       else
+               (api_compact <!> api_full) <.> response @Doc
+       where
+       api_compact =
+               (if full then helps l10n_help_opt_help_compact else id) $
+               tag "h" (just False)
+       api_full =
+               (if full then helps l10n_help_opt_help_full else id) $
+               tag "help" (just True)
+
+run_help lay = route :!: route
+       where
+       route helpInh_full = do
+               width <- Just . maybe 80 (min 80 . max 0 . (\x -> x - 2) . Console.width)
+                <$> Console.size
+               return $
+                       Doc.setWidth width $
+                               runLayout helpInh_full lay
+
+-- * Type 'Cfg_Global'
+data Cfg_Global
+ =   Cfg_Global
+ {   cfg_global_stderr_prepend_newline  :: Bool
+ ,   cfg_global_stderr_prepend_carriage :: Bool
+ ,   cfg_global_stderr_append_newline   :: Bool
+ ,   cfg_global_verbosity :: Verbosity
+ ,   cfg_global_lang      :: Lang
+ }
+
+api_options =
+       rule "OPTIONS" $
+               Cfg_Global False False True
+                <$> api_param_verbosity
+                <*> api_param_lang
+api_param_lang =
+       (\v -> Map.findWithDefault
+        (LocaleIn @Langs en_US)
+        (Text.pack $ List.takeWhile (\c -> Char.isAlphaNum c || c == '_') v)
+        (locales @Langs)) <$>
+       toPermDefault "en_US" (env "LANG")
+
+-- * Type 'Verbosity'
+data Verbosity
+ =   Verbosity_Error
+ |   Verbosity_Warning
+ |   Verbosity_Info
+ |   Verbosity_Debug
+ deriving (Eq,Ord)
+
+instance IOType Verbosity
+instance FromSegment Verbosity where
+       fromSegment = \case
+        "error"   -> return $ Right Verbosity_Error
+        "warning" -> return $ Right Verbosity_Warning
+        "info"    -> return $ Right Verbosity_Info
+        "debug"   -> return $ Right Verbosity_Debug
+        _ -> return $ Left "invalid verbosity"
+
+api_param_verbosity =
+       helps l10n_help_opt_verbosity $
+       toPermDefault Verbosity_Info $
+               tag "verbosity" (
+                       constant "error"   Verbosity_Error   `alt`
+                       constant "warning" Verbosity_Warning `alt`
+                       constant "info"    Verbosity_Info    `alt`
+                       constant "debug"   Verbosity_Debug
+                ) `alt`
+               env "TEXTPHILE_VERBOSITY"
+
+outputMessage :: MC.MonadExec IO m => Cfg_Global -> Doc -> Doc -> m ()
+outputMessage Cfg_Global{..} hdr msg =
+       MC.exec @IO $ output $ OnHandle IO.stderr $ (`runReader` loqualize cfg_global_lang) $
+               (if cfg_global_stderr_prepend_newline then Doc.newline else mempty) <>
+               (if cfg_global_stderr_prepend_carriage then "\r" else mempty) <>
+               hdr<>": "<>msg<>
+               (if cfg_global_stderr_append_newline then Doc.newline else mempty)
+
+outputError :: MC.MonadExec IO m => Cfg_Global -> Doc -> MaybeT m a
+outputError cfg_global@Cfg_Global{..} msg = do
+       when (Verbosity_Error <= cfg_global_verbosity) $ do
+               outputMessage cfg_global (Doc.redder "ERROR") msg
+       empty
+
+outputWarning :: MC.MonadExec IO m => Cfg_Global -> Doc -> m ()
+outputWarning cfg_global@Cfg_Global{..} msg = do
+       when (Verbosity_Warning <= cfg_global_verbosity) $ do
+               outputMessage cfg_global (Doc.yellower "WARNING") msg
+
+outputInfo :: MC.MonadExec IO m => Cfg_Global -> Doc -> m ()
+outputInfo cfg_global@Cfg_Global{..} msg = do
+       when (Verbosity_Info <= cfg_global_verbosity) $ do
+               outputMessage cfg_global (Doc.greener "info") msg
+
+outputDebug :: MC.MonadExec IO m => Cfg_Global -> Doc -> m ()
+outputDebug cfg_global@Cfg_Global{..} msg = do
+       when (Verbosity_Debug <= cfg_global_verbosity) $ do
+               outputMessage cfg_global (Doc.magentaer "debug") msg
diff --git a/cli/Textphile/HLint.hs b/cli/Textphile/HLint.hs
new file mode 120000 (symlink)
index 0000000..ab18269
--- /dev/null
@@ -0,0 +1 @@
+../HLint.hs
\ No newline at end of file
index 98fca3fd497cdc17ce1638c86e643bd34d7b2343..1c9b63696765386dcffae6afaa1d9a50df92c31a 100644 (file)
@@ -75,7 +75,7 @@ import qualified Textphile.DTC.Write.Plain as Plain
 import qualified Textphile.TCT.Cell as TCT
 import qualified Textphile.Utils as FS
 import qualified Textphile.XML as XML
-import qualified Paths_hdoc as Textphile
+import qualified Paths_textphile as Textphile
 import Debug.Trace
 
 debug :: Show a => String -> a -> a
index 73c7a7a1d55df0076efd43a89d1ee07942956d3a..d362c316dc1c727382a9b6dc9d82c2c3deae063e 100644 (file)
@@ -2,7 +2,7 @@ name: textphile
 -- PVP:  +-+------- breaking API changes
 --       | | +----- non-breaking API additions
 --       | | | +--- code changes with no API change
-version: 0.0.0.20180213
+version: 0.0.0.20200303
 category: Language
 synopsis: Library and tools for technical and convivial documents
 stability: experimental
@@ -122,7 +122,6 @@ Library
     , judgmentphile-majority >= 0.0
     , hxt-charproperties >= 9.2
     , localization >= 1.0.1
-    , symantic-cli >= 0.0.0
     , symantic-xml >= 0.0.0
     , megaparsec >= 7.0.4
     , mono-traversable >= 1.0
@@ -145,6 +144,7 @@ Executable textphile
     Textphile.CLI
     Textphile.CLI.Compile
     Textphile.CLI.Lang
+    Textphile.CLI.Source
     Textphile.CLI.Utils
   default-language: Haskell2010
   default-extensions:
@@ -201,7 +201,7 @@ Executable textphile
     , safe >= 0.3
     , semigroups >= 0.18
     , symantic-document >= 0.1
-    , symantic-cli >= 0.0
+    , symantic-cli >= 2.4.3
     , symantic-xml >= 0.0
     , strict >= 0.3
     , terminal-size >= 0.3