module TreeGrepper.Comment ( Comment (..), getComments, CommentStyle (..), uncomment, comment, ) where import Control.Exception (throw) import Data.Aeson qualified as A import Data.Binary (Binary) import Data.ByteString.Lazy.Char8 qualified as B import Data.Function ((&)) import Data.List (find) import Data.Maybe (fromMaybe) import Data.Text qualified as T import Exception qualified as E import GHC.Generics (Generic) import Process (proc, sh) import System.FilePath (takeExtension) import System.Process.Typed (setWorkingDir) import TreeGrepper.FileType (FileType (..)) import TreeGrepper.FileType qualified as G import TreeGrepper.Match (Match (..), Position (..)) import TreeGrepper.Match qualified as G import TreeGrepper.Result (Result (..)) import TreeGrepper.Result qualified as G data Comment = Comment { -- result fields file :: String, file_type :: FileType, -- match fields kind :: String, name :: String, text :: T.Text, start :: Position, end :: Position } deriving (Show, Generic) data CommentStyle = LineStyle T.Text | BlockStyle T.Text T.Text deriving (Eq, Show, Generic, Binary) comment :: CommentStyle -> T.Text -> T.Text comment (LineStyle linePrefix) = T.unlines . map ((linePrefix <> " ") <>) . T.lines comment (BlockStyle blockStart blockEnd) = (blockStart <>) . (<> blockEnd) uncomment :: FileType -> T.Text -> (CommentStyle, T.Text) uncomment fileType rawText = maybe ( ( LineStyle info.lineStart, stripLineComments (G.info fileType).lineStart text ) ) ( \(blockInfo, blockStart) -> ( BlockStyle blockStart blockInfo.blockEnd, stripBlockComment blockStart blockInfo.blockEnd text ) ) $ do blockInfo <- info.block (,) blockInfo <$> find (`T.isPrefixOf` text) blockInfo.blockStart where info = G.info fileType text = stripLines rawText stripLines = T.intercalate "\n" . map T.strip . T.lines stripLineComments :: T.Text -> T.Text -> T.Text stripLineComments lineStart text = onLines ( \line -> fromMaybe line . fmap T.stripStart $ T.stripPrefix lineStart line ) text where onLines f = T.intercalate "\n" . map f . T.lines stripBlockComment :: T.Text -> T.Text -> T.Text -> T.Text stripBlockComment blockStart blockEnd text = T.strip . (fromMaybe text . T.stripSuffix blockEnd) . (fromMaybe text . T.stripPrefix blockStart) $ text fromMatch :: Result -> Match -> Comment fromMatch Result {..} Match {..} = Comment {..} getComments :: FilePath -> FilePath -> IO [Comment] getComments cwd fn = do let ext = takeExtension fn concatMap (\result -> map (fromMatch result) result.matches) . map fixTreeGrepper . decode <$> sh ( proc "tree-grepper --query % % --format json %" (treeGrepperLanguage ext) (treeGrepperQuery ext) fn & setWorkingDir cwd ) decode :: B.ByteString -> [Result] decode = either (throw . E.InvalidTreeGrepperResult) id . A.eitherDecode fixTreeGrepper :: G.Result -> G.Result fixTreeGrepper treeGrepperResult = treeGrepperResult {G.matches = G.merge treeGrepperResult.matches} treeGrepperLanguage :: String -> String treeGrepperLanguage ext = -- TODO Add support for all tree-grepper supported files -- -- tree-grepper supported files can be listed through `tree-grepper -- --languages`. -- -- @backlog case ext of ".elm" -> "elm" ".hs" -> "haskell" ".nix" -> "nix" ".sh" -> "sh" _ -> throw (E.UnknownFileExtension ext) treeGrepperQuery :: String -> String treeGrepperQuery ext = case ext of ".elm" -> "([(line_comment) (block_comment)])" ".hs" -> "(comment)" ".nix" -> "(comment)" ".sh" -> "(comment)" _ -> throw (E.UnknownFileExtension ext)