{-# LANGUAGE AllowAmbiguousTypes #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Main where -- 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.Locale import Data.Maybe (Maybe(..), fromMaybe, maybe) import Data.Monoid (Monoid(..)) import Data.Ord (Ord(..)) import Data.Semigroup (Semigroup(..)) import Data.String (String, IsString(..)) import Data.Void (Void) import GHC.Exts (IsList(..)) import Prelude (error) 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.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 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 Language.Symantic.CLI hiding (main) import qualified Language.Symantic.CLI as CLI import qualified Language.Symantic.Document.Term.IO as Doc 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 <- 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" 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 = 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 } deriving (Show) -- *** Type 'CommandSourceFormat' data CommandSourceFormat = CommandSourceFormat_Plain | CommandSourceFormat_HTML5 deriving (Show) instance Default CommandSourceFormat where def = CommandSourceFormat_Plain -- *** 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) -- * 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 } -- * 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 " , "" , "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 " , "" , "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