{-# LANGUAGE CPP #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Commonmark
import Commonmark.Extensions
import Commonmark.Pandoc
import Data.Maybe (isJust)
import Data.Aeson (encode)
import qualified Data.Sequence as Seq
import qualified Data.ByteString.Lazy as BL
import qualified Text.Pandoc.Builder as B
import Control.Monad
import Control.Monad.Identity
import Control.Monad.State
import Data.Typeable
import qualified Data.Text.IO as TIO
import qualified Data.Text.Lazy.IO as TLIO
import qualified Data.Map as M
import qualified Data.Text as T
import System.Environment
import System.Exit
import System.IO
import System.Console.GetOpt
import Paths_commonmark_cli (version)
import Data.Version (showVersion)
#if !MIN_VERSION_base(4,11,0)
import Data.Monoid
#endif
import Control.Exception (AsyncException, catch, throwIO)
import GHC.Stack (currentCallStack)
import System.Console.ANSI
data Opt =
Help
| Version
| Tokenize
| SourcePos
| Highlight
| PandocJSON
| ListExtensions
| Extension String
deriving Eq
options :: [OptDescr Opt]
options =
[ Option ['t'] ["tokenize"] (NoArg Tokenize) "tokenize"
, Option ['x'] ["extension"] (ReqArg Extension "extension") "use extension"
, Option ['p'] ["sourcepos"] (NoArg SourcePos) "source positions"
, Option [] ["highlight"] (NoArg Highlight) "highlight using ANSI"
, Option ['j'] ["json"] (NoArg PandocJSON) "pandoc JSON output"
, Option ['v'] ["version"] (NoArg Version) "version info"
, Option [] ["list-extensions"] (NoArg ListExtensions) "list extensions"
, Option ['h'] ["help"] (NoArg Help) "help message"
]
usageMessage :: String -> [OptDescr Opt] -> String
usageMessage programName = usageInfo (programName ++ " [OPTIONS] [FILES]")
main :: IO ()
main = catch (do
(opts, files, errs) <- getOpt Permute options <$> getArgs
unless (null errs) $ do
mapM_ (hPutStrLn stderr) errs
exitWith (ExitFailure 1)
prg <- getProgName
when (Help `elem` opts) $ do
putStr (usageMessage prg options)
exitSuccess
when (ListExtensions `elem` opts) $ do
listExtensions
exitSuccess
when (Version `elem` opts) $ do
putStrLn $ prg ++ " " ++ showVersion version
exitSuccess
toks <- if null files
then tokenize "stdin" <$> TIO.getContents
else mconcat <$> mapM (\f -> tokenize f <$> TIO.readFile f) files
when (Tokenize `elem` opts) $ do
print toks
exitSuccess
if Highlight `elem` opts then do
spec <- specFromExtensionNames [x | Extension x <- opts]
case runWithSourceMap <$>
runIdentity (parseCommonmarkWith spec toks) of
Left e -> errExit e
Right (_ :: Html (), sm) -> highlightWith sm toks
else
if SourcePos `elem` opts then do
spec <- specFromExtensionNames [x | Extension x <- opts]
case runIdentity (parseCommonmarkWith spec toks) of
Left e -> errExit e
Right (r :: Html SourceRange)
-> TLIO.putStr . renderHtml $ r
else
if PandocJSON `elem` opts then do
spec <- specFromExtensionNames [x | Extension x <- opts]
case runIdentity (parseCommonmarkWith spec toks) of
Left e -> errExit e
Right (r :: Cm () B.Blocks) -> do
BL.putStr . encode $ B.doc $ unCm r
BL.putStr "\n"
else do
spec <- specFromExtensionNames [x | Extension x <- opts]
case runIdentity (parseCommonmarkWith spec toks) of
Left e -> errExit e
Right (r :: Html ()) -> TLIO.putStr . renderHtml $ r)
(\(e :: AsyncException) -> do
currentCallStack >>= mapM_ (hPutStrLn stderr)
throwIO e)
errExit :: ParseError -> IO a
errExit err = do
hPrint stderr err
exitWith (ExitFailure 1)
extensions :: (Monad m, Typeable m,
Typeable bl, Typeable il,
IsBlock il bl, IsInline il,
HasPipeTable il bl,
HasMath il,
HasEmoji il,
HasSpan il,
ToPlainText il,
HasStrikethrough il,
HasSuperscript il,
HasSubscript il,
HasDefinitionList il bl,
HasDiv bl,
HasTaskList il bl,
HasFootnote il bl)
=> [(String, SyntaxSpec m il bl)]
extensions =
[ ("autolinks", autolinkSpec)
,("pipe_tables", pipeTableSpec)
,("hard_line_breaks", hardLineBreaksSpec)
,("strikethrough", strikethroughSpec)
,("superscript", superscriptSpec)
,("subscript", subscriptSpec)
,("smart", smartPunctuationSpec)
,("math", mathSpec)
,("emoji", emojiSpec)
,("footnotes", footnoteSpec)
,("definition_lists", definitionListSpec)
,("fancy_lists", fancyListSpec)
,("task_lists", taskListSpec)
,("attributes", attributesSpec)
,("raw_attribute", rawAttributeSpec)
,("bracketed_spans", bracketedSpanSpec)
,("fenced_divs", fencedDivSpec)
,("auto_identifiers", autoIdentifiersSpec)
,("auto_identifiers_ascii", autoIdentifiersAsciiSpec)
,("implicit_heading_references", implicitHeadingReferencesSpec)
,("gfm", gfmExtensions)
]
extensionList :: [String]
extensionList = map fst
(extensions :: [(String, SyntaxSpec IO (Html ()) (Html ()))])
listExtensions :: IO ()
listExtensions =
putStr $ "Available extensions (or 'all' for all):\n" ++
unlines (map (" " ++) extensionList)
specFromExtensionNames ::
(Monad m, Typeable m, Typeable bl, Typeable il,
IsBlock il bl, IsInline il,
HasPipeTable il bl, HasMath il, HasEmoji il,
HasSpan il,
ToPlainText il,
HasStrikethrough il,
HasSuperscript il,
HasSubscript il,
HasDefinitionList il bl,
HasDiv bl,
HasTaskList il bl,
HasFootnote il bl)
=> [String] -> IO (SyntaxSpec m il bl)
specFromExtensionNames extnames = do
let extFromName name =
case lookup name extensions of
Just ext -> return ext
Nothing -> do
hPutStrLn stderr $ "Unknown extension " ++ name
listExtensions
exitWith (ExitFailure 1)
exts <- if "all" `elem` extnames
then return $ mconcat (map snd extensions)
else mconcat <$> mapM extFromName extnames
return $ exts <> defaultSyntaxSpec
highlightWith :: SourceMap -> [Tok] -> IO ()
highlightWith sm ts = evalStateT (mapM_ (hlTok sm) ts) (mempty, [])
hlTok :: SourceMap -> Tok
-> StateT (Seq.Seq T.Text, [SGR]) IO ()
hlTok (SourceMap !sm) (Tok toktype !pos !t) = do
(xs, sgrs) <- get
-- When we encounter a line end, we store it in mbLineEnd
-- and output it after the formatting codes at the beginning
-- of the next line. Otherwise the wrong lines are affected.
case M.lookup pos sm of
Nothing -> liftIO $ do
when (toktype == LineEnd) $
-- This escape sequence paints rest of line with current
-- background color; otherwise we get bad results with scrolling.
TIO.putStr "\27[K"
TIO.putStr t
Just (starts, ends) -> do
let xsMinusEnds = foldr (\e s ->
case Seq.viewr s of
Seq.EmptyR -> s
z Seq.:> x
| x == e -> z
| otherwise -> s) xs (Seq.reverse ends)
let xs' = xsMinusEnds <> starts
let isStr = xs' `has` "str"
let sgrs' = if xs == xs'
then sgrs
else Reset :
SetConsoleIntensity
(if isStr
then NormalIntensity
else FaintIntensity) :
foldMap sgrFrom xs'
put (xs', sgrs')
liftIO $ do
when (sgrs /= sgrs') $ setSGR sgrs'
when (toktype == LineEnd) $
TIO.putStr "\27[K"
TIO.putStr t
sgrFrom :: T.Text -> [SGR]
sgrFrom t =
case t of
"link" -> [SetUnderlining SingleUnderline]
"image" -> [SetColor Foreground Vivid Magenta]
"entity" -> [SetColor Foreground Vivid Magenta]
"escapedChar" -> [SetColor Foreground Vivid Magenta]
"code" -> [SetColor Foreground Vivid White,
SetColor Background Dull Cyan]
"codeBlock" -> [SetColor Foreground Vivid White,
SetColor Background Dull Cyan]
"rawInline" -> [SetColor Foreground Vivid Green]
"rawBlock" -> [SetColor Foreground Vivid Green]
"heading1" -> [SetColor Foreground Vivid Blue]
"heading2" -> [SetColor Foreground Vivid Blue]
"heading3" -> [SetColor Foreground Vivid Blue]
"heading4" -> [SetColor Foreground Vivid Blue]
"heading5" -> [SetColor Foreground Vivid Blue]
"heading6" -> [SetColor Foreground Vivid Blue]
"blockQuote" -> [SetColor Foreground Dull Cyan]
"referenceLinkDefinition"
-> [SetColor Foreground Dull Cyan,
SetUnderlining SingleUnderline]
"emoji" -> [SetColor Foreground Vivid Magenta]
"math" -> [SetColor Foreground Vivid Magenta]
"strikethrough" -> [SetColor Foreground Vivid Magenta]
"superscript" -> [SetColor Foreground Vivid Magenta]
"subscript" -> [SetColor Foreground Vivid Magenta]
"footnote" -> [SetColor Foreground Vivid Magenta]
"footnoteRef" -> [SetColor Foreground Vivid Magenta]
_ -> []
has :: Seq.Seq T.Text -> T.Text -> Bool
has xs t = isJust $ t `Seq.elemIndexL` xs