submark-0.3.0: app/Main.hs
{-# LANGUAGE DisambiguateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
import Data.Maybe
import System.IO
import CMark
import Data.Text hiding (empty, foldl)
import Data.Text.IO as TIO
import Options.Applicative
import Text.CommonMark.Sub
data Extraction = Extraction
{ outputFilePath :: FilePath
, headingPattern :: HeadingPattern
, ignoreCase :: Bool
, omitHeading :: Bool
, columnWidth :: Maybe Int
, inputFilePath :: FilePath
} deriving (Eq, Ord, Show)
headingPatternParser :: Parser HeadingPattern
headingPatternParser =
foldl (<|>) empty parsers
where
levels :: [Level]
levels = [1..6]
parsers :: [Parser HeadingPattern]
parsers = titleTextParsers ++ titleRegexParsers
titleTextParsers :: [Parser HeadingPattern]
titleTextParsers =
[ HeadingPattern l False . HeadingTitleText . pack <$> strOption
( long ("h" ++ show l)
<> metavar "TITLE"
<> help ("Extract the section with the heading level " ++
show l ++ ". Note that it tries to match to the " ++
"heading title with no markup, which means " ++
"--h1 \"foo bar\" matches to both `# foo bar' and " ++
"`# _foo_ **bar**'. Mutually exclusive with other " ++
"--h* and --h*-regex options.")
)
| l <- levels
]
titleRegexParsers :: [Parser HeadingPattern]
titleRegexParsers =
[ HeadingPattern l False . HeadingTitleRegex . pack <$> strOption
( long ("h" ++ show l ++ "-regex")
<> metavar "PATTERN"
<> help ("Similar to --h" ++ show l ++ " except that it takes " ++
"a regular expression. Note that it tries to match " ++
"to the heading title with no markup, which means " ++
"--h1 \"fo+ ba[rz]\" matches to both `# foo bar' and " ++
"`# _foooo_ **baz**'. Mutually exclusive with other " ++
"--h* and --h*-regex options.")
)
| l <- levels
]
parser :: Parser Extraction
parser = Extraction
-- CHECK: Write docs in README.md for a new option.
<$> strOption ( long "out-file"
<> short 'o'
<> metavar "FILE"
<> value "-"
<> showDefault
<> help "Write output to FILE."
)
<*> headingPatternParser
<*> switch ( long "ignore-case"
<> short 'i'
<> help "Ignore case distinctions."
)
<*> switch ( long "omit-heading"
<> short 'O'
<> help "Omit a leading heading."
)
<*> ( Just . read <$> strOption
( long "columns"
<> short 'c'
<> metavar "WIDTH"
<> help ("Limit the maximum characters per line of the output. " ++
"No limit by default.")
)
<|> pure Nothing
)
<**> helper
<*> strArgument ( metavar "FILE"
<> help "CommonMark/Markdown text to extract from."
)
parserInfo :: ParserInfo Extraction
parserInfo = info parser $
fullDesc <> progDesc "Extract a part from CommonMark/Markdown docs."
withInputFile :: Extraction -> (Handle -> IO r) -> IO r
withInputFile Extraction { inputFilePath = "-" } action' = do
let i = stdin
r <- action' i
hClose i
return r
withInputFile Extraction { inputFilePath = i } action' =
withFile i ReadMode action'
withOutputFile :: Extraction -> (Handle -> IO r) -> IO r
withOutputFile Extraction { outputFilePath = "-" } action' = do
let o = stdout
r <- action' o
hClose o
return r
withOutputFile Extraction { outputFilePath = o } action' =
withFile o WriteMode action'
extract :: Extraction -> IO ()
extract e@Extraction { headingPattern
, ignoreCase
, omitHeading
, columnWidth
} = do
text <- withInputFile e TIO.hGetContents
let doc = commonmarkToNode [] text
pat = headingPattern { caseInsensitive = ignoreCase }
node = case (omitHeading, extractSection pat doc) of
(True, Node p DOCUMENT (_ : xs)) -> Node p DOCUMENT xs
(_, other) -> other
result = nodeToCommonmark [] (Just $ fromMaybe (-1) columnWidth) node
withOutputFile e (`TIO.hPutStrLn` result)
main :: IO ()
main = execParser parserInfo >>= extract