packages feed

mdoc-0.1.0.0: src/Mdoc/Detect.hs

-- |
--
-- Module      : Mdoc.Detect
-- Copyright   : (c) 2026 Patrick Brisbin
-- License     : AGPL-3
-- Maintainer  : pbrisbin@gmail.com
-- Stability   : experimental
-- Portability : POSIX
module Mdoc.Detect
  ( Unparsable (..)
  , detectParsable
  , prettyUnparsable
  ) where

import Mdoc.Prelude

import Data.List.NonEmpty qualified as NE
import Data.Text qualified as T
import Mdoc.Pretty (Ann (..))
import Prettyprinter

data Unparsable
  = KnownBug
  | NotMdoc Text

prettyUnparsable :: Unparsable -> Doc Ann
prettyUnparsable = \case
  NotMdoc m ->
    "first macro is not"
      <+> annotate AnnKeyword ".Dd"
      <+> "or"
      <+> annotate AnnKeyword ".Dt"
      <+> " (saw "
      <> annotate AnnKeyword (pretty m)
      <> ")"
  KnownBug -> "known bug in this mdoc source"

detectParsable :: String -> Text -> Maybe Unparsable
detectParsable name txt =
  knownBug name <|> notMdoc (mapMaybe toMacro $ T.lines txt)

knownBug :: String -> Maybe Unparsable
knownBug name = KnownBug <$ guard (name `elem` knownBad)

knownBad :: [String]
knownBad =
  [ "/usr/share/man/man1/xkbcli-how-to-type.1.gz" -- https://github.com/xkbcommon/libxkbcommon/pull/863
  , "/usr/share/man/man3/archive_entry_paths.3.gz" -- https://github.com/libarchive/libarchive/pull/2746
  , "/usr/share/man/man5/crypt.5.gz" -- .if in .de macros
  ]

notMdoc :: [Text] -> Maybe Unparsable
notMdoc ms = do
  m <- NE.head <$> nonEmpty ms
  NotMdoc m <$ guard (m `notElem` [".Dd", ".Dt"])

toMacro :: Text -> Maybe Text
toMacro t
  | ".\\\"" `T.isPrefixOf` t = Nothing -- comment
  | "." == t = Nothing -- standalone dot
  | "." `T.isPrefixOf` t = NE.head <$> nonEmpty (T.words t)
  | otherwise = Nothing