Fix class= in <section>.
[doclang.git] / exe / cli / Main.hs
index ee6f3a9c5fa320f586fd5cc3925eae49082938cb..4026448b29d1062b467573c651dc53970247e79e 100644 (file)
-{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE AllowAmbiguousTypes #-}
 {-# LANGUAGE DuplicateRecordFields #-}
+{-# LANGUAGE ExistentialQuantification #-}
+{-# LANGUAGE FlexibleContexts #-}
 {-# LANGUAGE OverloadedLists #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
 {-# LANGUAGE TypeApplications #-}
 {-# LANGUAGE TypeFamilies #-}
-{-# LANGUAGE UndecidableInstances #-}
 {-# OPTIONS_GHC -fno-warn-orphans #-}
 module Main where
 
-import Control.Monad (forM_, when)
+-- import Data.Reflection (reify, Reifies(..))
+-- import qualified Data.Text.IO as Text
+import Control.Applicative (pure)
+import Control.Monad (Monad(..), forM_, when)
 import Data.Bool
 import Data.Default.Class (Default(..))
 import Data.Either (Either(..))
 import Data.Eq (Eq(..))
+import Data.Foldable (Foldable(..))
 import Data.Function (($), (.))
 import Data.Functor ((<$>))
-import Data.Map.Strict (Map)
-import Data.Maybe (Maybe(..), fromMaybe)
+import Data.Locale
+import Data.Maybe (Maybe(..), fromMaybe, maybe)
 import Data.Monoid (Monoid(..))
+import Data.Ord (Ord(..))
 import Data.Semigroup (Semigroup(..))
-import Data.String (String)
+import Data.String (String, IsString(..))
+import Data.Void (Void)
 import GHC.Exts (IsList(..))
-import Options.Applicative as Opt
 import Prelude (error)
-import System.IO (IO, FilePath, hPrint, hPutStrLn, stderr, stdout)
+import System.FilePath as FilePath
+import System.IO (IO, FilePath)
+import Text.Show (Show(..))
 import qualified Data.ByteString as BS
+import qualified Data.ByteString.Lazy as BSL
 import qualified Data.Char as Char
 import qualified Data.List as List
 import qualified Data.Map.Strict as Map
+import qualified Data.Set as Set
 import qualified Data.Text as Text
-import qualified Data.Text.IO as Text
 import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Encoding as TL
 import qualified Data.Text.Lazy.IO as TL
+import qualified Data.TreeSeq.Strict as Tree
+import qualified Data.Version as Version
+import qualified Paths_hdoc as Hdoc
+import qualified System.Directory as IO
 import qualified System.Environment as Env
+import qualified System.IO as IO
+import qualified System.IO.Error as IO
 import qualified Text.Blaze.Renderer.Utf8 as Blaze
 import qualified Text.Blaze.Utils as Blaze
+import qualified Text.Megaparsec as P
 
-import Data.Locale
+import qualified Language.TCT             as TCT
+import qualified Language.TCT.Write.HTML5 as TCT
+import qualified Language.TCT.Write.Plain as TCT
+import qualified Language.TCT.Write.XML   as TCT
+import qualified Language.DTC.Read.TCT    as DTC
+import qualified Language.DTC.Sym         as DTC
+import qualified Language.DTC.Write.HTML5 as DTC
+import qualified Language.DTC.Write.XML   as DTC
+import qualified Language.RNC.Write       as RNC
+import qualified Text.Blaze.DTC           as Blaze.DTC
+import qualified Text.Blaze.HTML5         as Blaze.HTML5
 
-import qualified Data.TreeSeq.Strict as Tree
-{-
-import qualified Language.DTC.Read.TCT as DTC.Read.TCT
-import qualified Language.DTC.Sym as DTC
-import qualified Language.DTC.Write.HTML5 as DTC.Write.HTML5
-import qualified Language.DTC.Write.XML   as DTC.Write.XML
-import qualified Text.Blaze.DTC   as Blaze.DTC
-import qualified Text.Blaze.HTML5 as Blaze.HTML5
--}
--- import qualified Language.RNC.Write as RNC
-import qualified Language.TCT as TCT
-import qualified Language.TCT.Write.Plain as TCT.Write.Plain
-import qualified Language.TCT.Write.HTML5 as TCT.Write.HTML5
-import qualified Language.TCT.Write.XML   as TCT.Write.XML
-import qualified Text.Megaparsec  as P
-
-import Read
+import Language.Symantic.CLI hiding (main)
+import qualified Language.Symantic.CLI as CLI
+import qualified Language.Symantic.Document.Term.IO as Doc
 
-type Langs = '[FR, EN]
-type Lang = LocaleIn Langs
+import qualified Language.Symantic.CLI.Plain as Plain
+import qualified Language.Symantic.CLI.Help  as Help
+import qualified Language.Symantic.CLI.Read  as Read
+
+version :: TL.Text
+version = "hdoc-" <> TL.pack (Version.showVersion Hdoc.version)
 
 main :: IO ()
 main = do
-       lang <-
-               (\v -> Map.findWithDefault
-                (LocaleIn @Langs en_US)
-                (Text.pack $ List.takeWhile (\c -> Char.isAlphaNum c || c == '_') v)
-                (locales @Langs)) .
-               fromMaybe ""
-                <$> Env.lookupEnv "LANG"
-       cmd <- execParser $ pArgv lang
-       mainWithCommand cmd
-       where
-       pArgv lang =
-               info (pCommand lang <**> helper) $ mconcat
-                [ fullDesc
-                , progDesc "document tool"
-                , header "hdoc - TCT and DTC command line tool"
-                ]
+       lang <- getLang
+       args <- Env.getArgs
+       readArgs lang args >>= \case
+        Nothing -> return ()
+        Just (Left  err) -> onExit err
+        Just (Right cmd) -> onCommand cmd
+
+readArgs ::
+ forall d.
+ Plain.Doc d =>
+ Lang -> [String] -> IO.IO (Maybe (Either (Exit (Doc d)) Command))
+readArgs lang args =
+       case
+               Read.readArgs (cli (loqualize lang) lang) $
+               Read.Args $ Read.Arg <$> ("hdoc":args) of
+        Right a -> return $ Just $ Right a
+        Left err ->
+               case err of
+                P.FancyError pos es ->
+                       case Set.toList es of
+                        [P.ErrorCustom (Read.ErrorRead e)] ->
+                               case e of
+                                Exit_Error ee -> do
+                                       IO.hPutStr IO.stderr $
+                                               P.parseErrorPretty @Read.Arg @(Read.ErrorRead Error) $
+                                               P.FancyError pos $ Set.singleton $ P.ErrorCustom $
+                                               Read.ErrorRead ee
+                                       return Nothing
+                                _ -> return $ Just $ Left e
+                        _ -> return Nothing
+                P.TrivialError pos e es -> do
+                       IO.hPutStr IO.stderr $
+                               P.parseErrorPretty @_ @Void $
+                               P.TrivialError pos e es
+                       return Nothing
+
+-- * Type 'Exit'
+data Exit d
+ =   Exit_Help d
+ |   Exit_Version
+ |   Exit_License (Loq d)
+ |   Exit_Error Error
+ deriving Show
+instance Show (Loqualization q) where
+       show _ = "Loqualization"
 
-mainWithCommand :: Command -> IO ()
-mainWithCommand (CommandTCT ArgsTCT{..}) =
-       readFile input $ \_fp txt ->
-               case TCT.readTrees input $ TL.fromStrict txt of
-                Left err -> error $ P.parseErrorPretty err
-                Right tct -> do
-                       when (trace_TCT trace) $ do
-                               hPutStrLn stderr "### TCT ###"
-                               hPrint stderr $ Tree.Pretty tct
-                       when (trace_XML trace) $ do
-                               hPutStrLn stderr "### XML ###"
-                               let xml = TCT.Write.XML.xmlDocument tct
-                               hPrint stderr $ Tree.Pretty xml
-                       case format of
-                        TctFormatPlain ->
-                               TL.putStrLn $
-                               TCT.Write.Plain.plainDocument tct
-                        TctFormatHTML5 ->
-                               Blaze.renderMarkupToByteStringIO BS.putStr $
-                               TCT.Write.HTML5.html5Document tct
-{-
-mainWithCommand (CommandDTC ArgsDTC{..}) =
-       readFile input $ \_fp txt ->
-               case TCT.readTCTs input txt of
-                Left err -> error $ P.parseErrorPretty err
-                Right tct -> do
-                       when (trace_TCT trace) $ do
-                               hPutStrLn stderr "### TCT ###"
-                               hPrint stderr $ Tree.Pretty tct
-                       let xml = TCT.Write.XML.xmlDocument tct
-                       when (trace_XML trace) $ do
-                               hPutStrLn stderr "### XML ###"
-                               hPrint stderr $ Tree.Pretty xml
-                       case DTC.Read.TCT.readDTC xml of
-                        Left err -> error $ P.parseErrorPretty err
-                        Right dtc -> do
-                               when (trace_DTC trace) $ do
-                                       hPutStrLn stderr "### DTC ###"
-                                       hPrint stderr dtc
-                               case format of
-                                DtcFormatXML ->
-                                       Blaze.prettyMarkupIO Blaze.DTC.indentTag BS.putStr $
-                                       DTC.Write.XML.xmlDocument locale dtc
-                                DtcFormatHTML5 ->
-                                       Blaze.prettyMarkupIO Blaze.HTML5.indentTag BS.putStr $
-                                       DTC.Write.HTML5.html5Document locale dtc
-mainWithCommand (CommandRNC ArgsRNC{}) =
-       forM_ DTC.dtcRNC $ \w ->
-               Text.hPutStrLn stdout $ RNC.renderWriter w
--}
-
--- * Options utils
-
-instance IsList (Opt.Mod f a) where
-       type Item (Opt.Mod f a) = Opt.Mod f a
-       fromList = mconcat
-       toList   = pure
-
-readMap :: Map String a -> ReadM a
-readMap m =
-       eitherReader $ \s ->
-               case Map.lookup s m of
-                Nothing -> Left $ "cannot parse value \"" <> s
-                        <> "\"\nexpecting one of: "
-                        <> (List.intercalate ", " $ Map.keys m)
-                Just a -> Right a
+onExit :: Exit (Doc Doc.TermIO) -> IO ()
+onExit (Exit_Help d) =
+       Doc.runTermIO IO.stdout $
+       Doc.withBreakable (Just 80) (runDoc d) <>
+       Doc.newline
+onExit Exit_Version =
+       TL.putStrLn version
+onExit (Exit_License (Loqualization l)) =
+       Doc.runTermIO IO.stdout $
+       runDoc $
+       l10n_license l
+onExit Exit_Error{} =
+       return ()
+
+-- ** Type 'Error'
+data Error
+ =   Error_Locale String
+ deriving Show
 
 -- * Type 'Command'
 data Command
- =   CommandTCT ArgsTCT
- {-
- |   CommandDTC ArgsDTC
- |   CommandRNC ArgsRNC
- -}
-
-pCommand :: Lang -> Parser Command
-pCommand lang =
-       hsubparser
-        [ metavar "tct"
-        , command "tct" $
-               info (CommandTCT <$> pArgsTCT) $
-                       progDesc "TCT (Texte Convivial Technique) rendition."
-        ] {-<|>
-       hsubparser
-        [ metavar "dtc"
-        , command "dtc" $
-               info (CommandDTC <$> pArgsDTC lang) $
-                       progDesc "DTC (Document Technique Convivial) rendition."
-        ] <|>
-       hsubparser
-        [ metavar "rnc"
-        , command "rnc" $
-               info (CommandRNC <$> pArgsRNC) $
-                       progDesc "RNC (RelaxNG Compact) schema."
-        ]-}
-
--- * Type 'Trace'
-data Trace
- =   Trace
- {   trace_TCT :: Bool
- ,   trace_XML :: Bool
- ,   trace_DTC :: Bool
+ =   Command_Source  CommandSource
+ |   Command_Compile CommandCompile
+ |   Command_Schema  CommandSchema
+ deriving (Show)
+
+onCommand :: Command -> IO ()
+onCommand cmd@(Command_Source CommandSource{..}) = do
+       IO.hPrint IO.stderr cmd
+       TCT.readTCT source_input >>= \case
+        Left err -> error $ show err
+        Right tct -> do
+               when source_dump_tct $
+                       writeFile (source_output-<.>"tct.dump") $
+                               TL.pack $ Tree.prettyTrees tct
+               case source_format of
+                CommandSourceFormat_Plain ->
+                       writeFile source_output $
+                       TCT.writePlain tct
+                CommandSourceFormat_HTML5 ->
+                       withFile source_output IO.WriteMode $ \h ->
+                               Blaze.renderMarkupToByteStringIO (BS.hPutStr h) $
+                               TCT.writeHTML5 tct
+onCommand cmd@(Command_Compile CommandCompile{..}) = do
+       IO.hPrint IO.stderr cmd
+       TCT.readTCT compile_input >>= \case
+        Left err -> error $ show err
+        Right tct -> do
+               when compile_dump_tct $ do
+                       writeFile (compile_output-<.>"tct.dump") $
+                               TL.pack $ Tree.prettyTrees tct
+               let xml = TCT.writeXML tct
+               when compile_dump_xml $ do
+                       writeFile (compile_output-<.>"xml.dump") $
+                               TL.pack $ Tree.prettyTrees xml
+               case DTC.readDTC xml of
+                Left err -> do
+                       removeFile $ compile_output-<.>"deps"
+                       error $ P.parseErrorPretty err
+                Right dtc -> do
+                       when compile_dump_deps $ do
+                               writeFile (compile_output-<.>"deps") $
+                                       writeDependencies compile_input tct
+                       when compile_dump_xml $ do
+                               writeFile (compile_output-<.>"dtc.dump") $
+                                       TL.pack $ show dtc
+                       case compile_format of
+                        CommandCompileFormat_XML ->
+                               withFile compile_output IO.WriteMode $ \h ->
+                                       Blaze.prettyMarkupIO Blaze.DTC.isInlinedElement (BS.hPutStr h) $
+                                       DTC.writeXML compile_locale dtc
+                        CommandCompileFormat_HTML5{..} -> do
+                               config_css <- do
+                                       src <- Hdoc.getDataFileName "style/dtc-html5.css"
+                                       case compile_html5_output_css of
+                                        Nothing -> Right <$> readFile src
+                                        Just "" -> return $ Left ""
+                                        Just dst -> do
+                                               IO.createDirectoryIfMissing True $ FilePath.takeDirectory dst
+                                               IO.copyFile src dst
+                                               return $ Left dst
+                               let conf = DTC.Config
+                                        { DTC.config_css
+                                        , DTC.config_locale    = compile_locale
+                                        , DTC.config_generator = version
+                                        }
+                               withFile compile_output IO.WriteMode $ \h ->
+                                       Blaze.prettyMarkupIO Blaze.HTML5.isInlinedElement (BS.hPutStr h) $
+                                       DTC.writeHTML5 conf dtc
+onCommand Command_Schema{} =
+       forM_ DTC.schema $ \ru ->
+               TL.hPutStrLn IO.stdout $ RNC.renderWriter ru
+
+writeDependencies :: FilePath -> TCT.Roots -> TL.Text
+writeDependencies input tct =
+       let dir = FilePath.takeDirectory input in
+       TL.pack input <> ":" <>
+       foldMap
+        ( TL.pack
+        . ((" \\\n " <>)
+        . FilePath.normalise
+        . (dir </>)) )
+        (TCT.dependencies tct) <>
+       "\n"
+
+-- ** Type 'CommandSource'
+data CommandSource
+ =   CommandSource
+ {   source_output    :: FilePath
+ ,   source_dump_tct  :: Bool
+ ,   source_format    :: CommandSourceFormat
+ ,   source_input     :: FilePath
  }
-instance Default Trace where
-       def = Trace
-        { trace_TCT = False
-        , trace_XML = False
-        , trace_DTC = False
-        }
-instance Semigroup Trace where
-       x <> y =
-               Trace
-                { trace_TCT = trace_TCT x || trace_TCT y
-                , trace_XML = trace_XML x || trace_XML y
-                , trace_DTC = trace_DTC x || trace_DTC y
-                }
-instance Monoid Trace where
-       mempty  = def
-       mappend = (<>)
+ deriving (Show)
+
+-- *** Type 'CommandSourceFormat'
+data CommandSourceFormat
+ =   CommandSourceFormat_Plain
+ |   CommandSourceFormat_HTML5
+ deriving (Show)
+instance Default CommandSourceFormat where
+       def = CommandSourceFormat_Plain
 
-pTrace :: Parser Trace
-pTrace =
-       (mconcat <$>) $
-       many $
-       option
-        (readMap m)
-        [ long "trace"
-        , help $ "Print trace. (choices: "
-                <> (List.intercalate ", " $ Map.keys m) <> ")"
-        ]
-       where
-       m = Map.fromList
-        [ ("tct", def{trace_TCT=True})
-        , ("xml", def{trace_XML=True})
-        , ("dtc", def{trace_DTC=True})
-        ]
-
--- ** Type 'ArgsTCT'
-data ArgsTCT
- =   ArgsTCT
- {   input  :: FilePath
- ,   format :: TctFormat
- ,   trace  :: Trace
+-- *** Type 'CommandSourceDump'
+data CommandSourceDump
+ =   CommandSourceDump_TCT
+ |   CommandSourceDump_XML
+ deriving (Eq, Ord, Show)
+
+-- ** Type 'CommandCompile'
+data CommandCompile
+ =   CommandCompile
+ {   compile_output    :: FilePath
+ ,   compile_locale    :: Lang
+ ,   compile_dump_tct  :: Bool
+ ,   compile_dump_xml  :: Bool
+ ,   compile_dump_deps :: Bool
+ ,   compile_format    :: CommandCompileFormat
+ ,   compile_input     :: FilePath
+ -- ,   compile_dump     :: Set CommandCompileDump
  }
+ deriving (Show)
+
+-- *** Type 'CommandCompileFormat'
+data CommandCompileFormat
+ =   CommandCompileFormat_HTML5
+     { compile_html5_output_css :: Maybe FilePath
+     , compile_html5_dump_dtc   :: Bool
+     }
+ |   CommandCompileFormat_XML
+     {
+     }
+ deriving (Show)
+instance Default CommandCompileFormat where
+       def = CommandCompileFormat_HTML5
+        { compile_html5_output_css = def
+        , compile_html5_dump_dtc   = False
+        }
+
+-- *** Type 'CommandCompileDump'
+data CommandCompileDump
+ =   CommandCompileDump_TCT
+ |   CommandCompileDump_XML
+ |   CommandCompileDump_DTC
+ |   CommandCompileDump_Deps
+ deriving (Eq, Ord, Show)
+
+-- ** Type 'CommandSchema'
+data CommandSchema
+ =   CommandSchema
+ deriving (Show)
 
-pArgsTCT :: Parser ArgsTCT
-pArgsTCT =
-       ArgsTCT
-        <$> argument str (metavar "FILE")
-        <*> pTctFormat
-        <*> pTrace
-
--- *** Type 'TctFormat'
-data TctFormat
- =   TctFormatPlain
- |   TctFormatHTML5
-
-pTctFormat :: Parser TctFormat
-pTctFormat =
-       flag TctFormatPlain TctFormatPlain
-        [ long "plain"
-        , help "Render as plain text."
-        ] <|>
-       flag TctFormatHTML5 TctFormatHTML5
-        [ long "html5"
-        , help "Render as HTML5."
-        ]
-
--- ** Type 'ArgsDTC'
-data ArgsDTC
- =   ArgsDTC
- {   input  :: FilePath
- ,   format :: DtcFormat
- ,   locale :: Lang
- ,   trace  :: Trace
+-- * Class 'CLI'
+class
+ ( Sym_Fun repr
+ , Sym_App repr
+ , Sym_Alt repr
+ , Sym_AltApp repr
+ , Sym_Help d repr
+ , Sym_Rule repr
+ , Sym_Interleaved repr
+ , Sym_Command repr
+ , Sym_Option repr
+ , Sym_Exit repr
+ , Plain.Doc d
+ -- , Reifies lang
+ ) => CLI d repr where
+       cli :: Loq d -> Lang -> repr (Exit d) ArgCommand Command
+       cli loq@(Loqualization l) lang =
+               help @d (l10n_cli l) $
+               CLI.main "hdoc" $ opts **> cmds
+               where
+               opts =
+                       interleaved $
+                       (\_ _ -> ())
+                        <<$?   option_help loq (help_usage $ cli loq lang)
+                        <<|?>> option_version loq
+                        <<|?>> option_license loq
+               cmds =
+                       Command_Source  <$$> command_source  loq <||>
+                       Command_Compile <$$> command_compile loq lang <||>
+                       Command_Schema  <$$> command_schema  loq
+       
+       option_help :: Loq d -> d -> ((), repr (Exit d) ArgOption ())
+       option_help (Loqualization l) d =
+               ((),) $
+               help @d (l10n_help_opt_help l) $
+               opt (OptionName 'h' "help") $
+               exit $ Exit_Help d
+       option_version :: Loq d -> ((), repr (Exit d) ArgOption ())
+       option_version (Loqualization l) = ((),) $
+               help @d (l10n_help_version l) $
+               long "version" $ exit $ Exit_Version
+       option_license :: Loq d -> ((), repr (Exit d) ArgOption ())
+       option_license loq@(Loqualization l) = ((),) $
+               help @d (l10n_help_license l) $
+               long "license" $ exit $ Exit_License loq
+       option_input :: Loq d -> (FilePath, repr (Exit d) ArgOption FilePath)
+       option_input (Loqualization l) =
+               (mempty,) $
+               help @d (l10n_help_opt_input l) $
+               opt (OptionName 'i' "input") $
+               string $ l10n_var_file l
+       option_output :: Loq d -> (FilePath, repr (Exit d) ArgOption FilePath)
+       option_output (Loqualization l) =
+               (mempty,) $
+               help @d (l10n_help_opt_output l) $
+               opt (OptionName 'o' "output") $
+               string $ l10n_var_file l
+       option_lang :: Loq d -> Lang -> (Lang, repr (Exit d) ArgOption Lang)
+       option_lang (Loqualization l) lang =
+               (lang,) $
+               help @d (l10n_help_opt_lang l) $
+               long "lang" $
+               var (l10n_var_locale l) $ \s ->
+                       maybe (Left $ Exit_Error $ Error_Locale s) Right $
+                       Map.lookup (Text.pack s) $
+                       locales @Langs
+       option_dump_tct :: Loq d -> (Bool, repr (Exit d) ArgOption Bool)
+       option_dump_tct (Loqualization l) =
+               help @d (l10n_help_opt_dump_tct l) <$>
+               flag (OptionNameLong "dump-tct")
+       option_dump_xml :: Loq d -> (Bool, repr (Exit d) ArgOption Bool)
+       option_dump_xml (Loqualization l) =
+               help @d (l10n_help_opt_dump_xml l) <$>
+               flag (OptionNameLong "dump-xml")
+       option_dump_deps :: Loq d -> (Bool, repr (Exit d) ArgOption Bool)
+       option_dump_deps (Loqualization l) =
+               help @d (l10n_help_opt_dump_deps l) <$>
+               flag (OptionNameLong "dump-deps")
+       option_dump_dtc :: Loq d -> (Bool, repr (Exit d) ArgOption Bool)
+       option_dump_dtc (Loqualization l) =
+               help @d (l10n_help_opt_dump_dtc l) <$>
+               flag (OptionNameLong "dump-dtc")
+       
+       command_source :: Loq d -> repr (Exit d) ArgCommand CommandSource
+       command_source loq@(Loqualization l) =
+               help @d (l10n_help_command_source l) $
+               command "source" $
+               (interleaved $
+                       CommandSource
+                        <<$?   option_help loq (help_usage $ command_source loq)
+                        <<|?>> option_output   loq
+                        <<|?>> option_dump_tct loq)
+                <**> (command_source_plain loq
+                 <||> command_source_html5 loq)
+                <**> string (l10n_var_file l)
+       command_source_plain :: Loq d -> repr (Exit d) ArgCommand CommandSourceFormat
+       command_source_plain loq@(Loqualization l) =
+               help @d (l10n_help_format_plain l) $
+               command "plain" $
+               interleaved $
+               CommandSourceFormat_Plain
+                <<$?   option_help loq (help_usage $ command_source_plain loq)
+       command_source_html5 :: Loq d -> repr (Exit d) ArgCommand CommandSourceFormat
+       command_source_html5 loq@(Loqualization l) =
+               help @d (l10n_help_format_html5 l) $
+               command "html" $
+               interleaved $
+               CommandSourceFormat_HTML5
+                <<$?   option_help loq (help_usage $ command_source_html5 loq)
+       
+       command_compile :: Loq d -> Lang -> repr (Exit d) ArgCommand CommandCompile
+       command_compile loq@(Loqualization l) lang =
+               help @d (l10n_help_command_compile l) $
+               command "compile" $
+               (setDefault <$$>) $
+               (interleaved $
+                       CommandCompile
+                        <<$?   option_help loq (help_usage $ command_compile loq lang)
+                        <<|?>> option_output    loq
+                        <<|?>> option_lang      loq lang
+                        <<|?>> option_dump_tct  loq
+                        <<|?>> option_dump_xml  loq
+                        <<|?>> option_dump_deps loq)
+                <**> (command_compile_html5 loq
+                 <||> command_compile_xml   loq)
+                <**> string (l10n_var_file l)
+               where
+               setDefault a@CommandCompile{..}
+                | null compile_output = (a::CommandCompile){compile_output=compile_input-<.>fmt compile_format}
+                | otherwise = a
+               fmt = \case
+                CommandCompileFormat_XML{}   -> "xml"
+                CommandCompileFormat_HTML5{} -> "html"
+       command_compile_html5 :: Loq d -> repr (Exit d) ArgCommand CommandCompileFormat
+       command_compile_html5 loq@(Loqualization l) =
+               help @d (l10n_help_format_html5 l) $
+               command "html" $
+               interleaved $
+                       CommandCompileFormat_HTML5
+                        <<$?   option_help loq (help_usage $ command_compile_html5 loq)
+                        <<|?>> option_html5_output_css
+                        <<|?>> option_dump_dtc loq
+               where
+               option_html5_output_css =
+                       (Nothing,) $
+                       (Just <$$>) $
+                       help @d (l10n_help_opt_output_css l) $
+                       opt (OptionNameLong "output-css") $
+                       string $ l10n_var_file l
+       command_compile_xml :: Loq d -> repr (Exit d) ArgCommand CommandCompileFormat
+       command_compile_xml loq@(Loqualization l) =
+               help @d (l10n_help_format_xml l) $
+               command "xml" $
+               interleaved $
+               CommandCompileFormat_XML
+                <<$?   option_help loq (help_usage $ command_compile_xml loq)
+       
+       command_schema :: Loq d -> repr (Exit d) ArgCommand CommandSchema
+       command_schema loq@(Loqualization l) =
+               help @d (l10n_help_command_schema l) $
+               command "schema" $
+               interleaved $
+               CommandSchema
+                <<$? option_help loq (help_usage $ command_schema loq)
+instance Plain.Doc d => CLI d (Plain.Plain d)
+instance Plain.Doc d => CLI d Read.Parser
+instance Plain.Doc d => CLI d (Help.Help d)
+
+help_usage :: Plain.Doc d => Help.Help d (Exit d) t a -> d
+help_usage = Help.textHelp Help.defReader
+ { Help.reader_command_indent = 2
+ , Help.reader_option_indent  = 12
  }
-pArgsDTC :: Lang -> Parser ArgsDTC
-pArgsDTC lang =
-       ArgsDTC
-        <$> argument str (metavar "FILE")
-        <*> pDtcFormat
-        <*> pLocale lang
-        <*> pTrace
-
-pLocale :: Lang -> Parser (LocaleIn Langs)
-pLocale lang =
-       option
-        (maybeReader $ \s -> Map.lookup (Text.pack s) $ locales @Langs)
-        [ long "lang"
-        , help "Language."
-        , showDefault
-        , value lang
-        , metavar "LOCALE"
-        ]
-
--- *** Type 'DtcFormat'
-data DtcFormat
- =   DtcFormatHTML5
- |   DtcFormatXML
-
-pDtcFormat :: Parser DtcFormat
-pDtcFormat =
-       flag DtcFormatHTML5 DtcFormatHTML5
-        [ long "html5"
-        , help "Render as HTML5."
-        ] <|>
-       flag DtcFormatHTML5 DtcFormatXML
-        [ long "xml"
-        , help "Render as XML."
-        ]
-
--- ** Type 'ArgsRNC'
-data ArgsRNC
- =   ArgsRNC
-
-pArgsRNC :: Parser ArgsRNC
-pArgsRNC = pure ArgsRNC
-
-
-{-
-       Args
-        <$> strOption (  long "hello"
-                      <> metavar "TARGET"
-                      <> help "Target for the greeting")
-        <*> switch (  long "quiet"
-                   <> short 'q'
-                   <> help "Whether to be quiet")
-        <*> option auto ( long "enthusiasm"
-                        <> help "How enthusiastically to greet"
-                        <> showDefault
-                        <> value 1
-                        <> metavar "INT")
--}
+
+-- * Type 'Lang'
+-- | Supported locales
+type Langs = '[FR, EN]
+type Lang  = LocaleIn Langs
+
+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"
+
+-- ** Class 'L10n'
+type Loq d = Loqualization (L10n d)
+-- | Localization
+class L10n_Var lang => L10n d lang where
+       l10n_cli                  :: FullLocale lang -> d
+       l10n_license              :: FullLocale lang -> d
+       l10n_help_version         :: FullLocale lang -> d
+       l10n_help_license         :: FullLocale lang -> d
+       l10n_help_command_source  :: FullLocale lang -> d
+       l10n_help_command_compile :: FullLocale lang -> d
+       l10n_help_command_schema  :: FullLocale lang -> d
+       l10n_help_opt_lang        :: FullLocale lang -> d
+       l10n_help_opt_output      :: FullLocale lang -> d
+       l10n_help_opt_output_css  :: FullLocale lang -> d
+       l10n_help_opt_dump_tct    :: FullLocale lang -> d
+       l10n_help_opt_dump_xml    :: FullLocale lang -> d
+       l10n_help_opt_dump_deps   :: FullLocale lang -> d
+       l10n_help_opt_dump_dtc    :: FullLocale lang -> d
+       l10n_help_format          :: FullLocale lang -> d
+       l10n_help_format_plain    :: FullLocale lang -> d
+       l10n_help_format_html5    :: FullLocale lang -> d
+       l10n_help_format_xml      :: FullLocale lang -> d
+       l10n_help_opt_input       :: FullLocale lang -> d
+       l10n_help_opt_help        :: FullLocale lang -> d
+class L10n_Var lang where
+       l10n_var_file   :: FullLocale lang -> Name
+       l10n_var_locale :: FullLocale lang -> Name
+
+-- * Type 'Doc'
+data Doc d
+ =   Doc d
+ |   Var Name
+ deriving Show
+instance (Semigroup d, IsString d) => Semigroup (Doc d) where
+       Doc x <> Doc y = Doc (x<>y)
+       x <> y = Doc $ runDoc x <> runDoc y
+instance (Semigroup d, Monoid d, IsString d) => Monoid (Doc d) where
+       mempty  = Doc mempty
+       mappend = (<>)
+instance Doc.Breakable d => IsString (Doc d) where
+       fromString = Doc . Plain.words
+instance (IsString d, Semigroup d, Monoid d) => IsList (Doc d) where
+       type Item (Doc d) = Doc d
+       toList = pure
+       fromList = Doc . foldMap runDoc
+instance (IsString d, Semigroup d) => Doc.Trans (Doc d) where
+       type ReprOf (Doc d) = d
+       trans   = Doc
+       unTrans = runDoc
+instance Doc.Breakable d => Doc.Textable (Doc d)
+instance (Doc.Breakable d, Doc.Indentable d) => Doc.Indentable (Doc d)
+instance Doc.Breakable d => Doc.Breakable (Doc d)
+instance (IsString d, Semigroup d, Doc.Decorable d) => Doc.Decorable (Doc d)
+instance (IsString d, Semigroup d, Doc.Colorable d) => Doc.Colorable (Doc d)
+instance Plain.Doc d => Plain.Doc (Doc d)
+
+runDoc :: (IsString d, Semigroup d) => Doc d -> d
+runDoc = \case
+ Doc d -> d
+ Var n -> "<"<>fromString n<>">"
+
+instance (IsString d, Semigroup d, Monoid d, Doc.Breakable d) => L10n (Doc d) EN where
+       l10n_cli _ = "Compiler of Convivial Technical Document (DTC)."
+       l10n_license _ =
+               fromString $
+               List.intercalate "\n"
+                [ "License: GNU GPLv3+"
+                , "Copyright: Julien Moutinho <julm+hdoc@autogeree.net>"
+                , ""
+                , "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 = ["Use the language given by ", Var $ l10n_var_locale l, "."]
+       l10n_help_opt_output      l = ["Output document into ", Var $ l10n_var_file l]
+       l10n_help_opt_output_css  l = [ "Output CSS stylesheet into "
+                                     , Var $ l10n_var_file l
+                                     , " (if any), instead of incorporating it into the HTML."
+                                     ]
+       l10n_help_opt_dump_tct    _ = "Dump internal representation of TCT."
+       l10n_help_opt_dump_xml    _ = "Dump internal representation of XML."
+       l10n_help_opt_dump_deps   _ = "Dump dependencies, in Makefile format."
+       l10n_help_opt_dump_dtc    _ = "Dump internal representation of DTC."
+       l10n_help_opt_help        _ = "Show this help."
+       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 = ["Read input from ", Var $ l10n_var_file l, "."]
+instance (IsString d , Semigroup d, Monoid d, Doc.Breakable d) => L10n (Doc d) FR where
+       l10n_cli _ = "Compilateur de Document Technique Convivial (DTC)."
+       l10n_license _ =
+               fromString $
+               List.intercalate "\n"
+                [ "Licence : GPLv3+ GNU"
+                , "Droit d’auteur : Julien Moutinho <julm+hdoc@autogeree.net>"
+                , ""
+                , "hdoc 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."
+                , ""
+                , "hdoc 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 hdoc."
+                , "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 = ["Utilise le langage indiqué par ", Var $ l10n_var_locale l, "."]
+       l10n_help_opt_output      l = ["Écrit dans ", Var $ l10n_var_file l, "."]
+       l10n_help_opt_output_css  l = [ "Écrit la feuille de style CSS dans "
+                                     , Var $ l10n_var_file l
+                                     , ", au lieu de l’incorporer dans le HTML."
+                                     ]
+       l10n_help_opt_dump_tct    _ = "Écrit la représentation interne du TCT."
+       l10n_help_opt_dump_xml    _ = "Écrit la représentation interne du XML."
+       l10n_help_opt_dump_deps   _ = "Écrit les dépendences, au format Makefile."
+       l10n_help_opt_dump_dtc    _ = "Écrit la représentation interne du DTC."
+       l10n_help_opt_help        _ = "Affiche cette aide."
+       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 = ["Lit depuis ", Var $ l10n_var_file l, "."]
+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"
+
+-- * Filesystem utilities
+readFile :: FilePath -> IO TL.Text
+readFile fp = TL.decodeUtf8 <$> BSL.readFile fp
+
+writeFile :: FilePath -> TL.Text -> IO ()
+writeFile fp s = BSL.writeFile fp $ TL.encodeUtf8 s
+
+withFile :: FilePath -> IO.IOMode -> (IO.Handle -> IO a) -> IO a
+withFile = IO.withFile
+
+removeFile :: FilePath -> IO ()
+removeFile f =
+       IO.removeFile f `IO.catchIOError` \e ->
+               if IO.isDoesNotExistError e
+               then return ()
+               else IO.ioError e