{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeFamilies #-} module Headroom.FileSupportSpec ( spec ) where import Headroom.Configuration ( makeHeadersConfig , parseConfiguration ) import Headroom.Configuration.Types ( Configuration(..) , HeaderConfig(..) , HeaderSyntax(..) , HeadersConfig(..) ) import Headroom.Data.Regex ( re ) import Headroom.Embedded ( defaultConfig ) import Headroom.FileSupport import Headroom.FileSupport.Types ( FileInfo(..) ) import Headroom.FileSystem ( loadFile ) import Headroom.FileType.Types ( FileType(..) ) import Headroom.Variables ( mkVariables ) import RIO import RIO.FilePath ( () ) import Test.Hspec spec :: Spec spec = do let samplesDir = "test-data" "code-samples" lHeaderConfig pb pa = HeaderConfig ["hs"] 0 0 pb pa (LineComment "--") bHeaderConfig = bHeaderConfigM 0 0 bHeaderConfigM mb ma pb pa = HeaderConfig ["hs"] mb ma pb pa (BlockComment "{-|" "-}") describe "addHeader" $ do let fileInfo config = FileInfo Haskell config Nothing mempty it "adds header at the beginning of text" $ do let info = fileInfo $ bHeaderConfig [] [] header = "HEADER" sample = "1\n2\nbefore\nafter\n4\n" expected = "HEADER\n1\n2\nbefore\nafter\n4\n" addHeader info header sample `shouldBe` expected it "adds header at the beginning of text (with correct margins)" $ do let info = fileInfo $ bHeaderConfigM 2 2 [] [] header = "HEADER" sample = "1\n2\nbefore\nafter\n4\n" expected = "HEADER\n\n\n1\n2\nbefore\nafter\n4\n" addHeader info header sample `shouldBe` expected it "adds header at correct position" $ do let info = fileInfo $ bHeaderConfig [[re|^before|]] [[re|^after|]] header = "{-| HEADER -}" sample = "1\n2\nbefore\nafter\n4\n" expected = "1\n2\nbefore\n{-| HEADER -}\nafter\n4\n" addHeader info header sample `shouldBe` expected it "adds header at correct position (with correct margins)" $ do let info = fileInfo $ bHeaderConfigM 2 2 [[re|^before|]] [[re|^after|]] header = "{-| HEADER -}" sample = "1\n2\nbefore\nafter\n4\n" expected = "1\n2\nbefore\n\n\n{-| HEADER -}\n\n\nafter\n4\n" addHeader info header sample `shouldBe` expected it "adds header at the end of text (with correct margins)" $ do let info = fileInfo $ bHeaderConfigM 2 2 [[re|^before|]] [] header = "{-| HEADER -}" sample = "1\n2\nbefore" expected = "1\n2\nbefore\n\n\n{-| HEADER -}" addHeader info header sample `shouldBe` expected it "does nothing if header is already present" $ do let config = bHeaderConfig [[re|^before|]] [] header = "{-| HEADER -}" info = FileInfo Haskell config (Just (3, 3)) mempty sample = "1\n2\nbefore\n{-| OLDHEADER -}\nafter\n4" addHeader info header sample `shouldBe` sample describe "dropHeader" $ do it "does nothing if no header is present" $ do let config = bHeaderConfig [] [] info = FileInfo Haskell config Nothing mempty sample = "1\n2\nbefore\nafter\n4\n" dropHeader info sample `shouldBe` sample it "drops existing single line header" $ do let config = bHeaderConfig [] [] info = FileInfo Haskell config (Just (3, 3)) mempty sample = "1\n2\nbefore\n{-| HEADER -}\nafter\n4\n" expected = "1\n2\nbefore\nafter\n4\n" dropHeader info sample `shouldBe` expected it "drops existing multi line header" $ do let config = bHeaderConfig [] [] info = FileInfo Haskell config (Just (3, 4)) mempty sample = "1\n2\nbefore\n{-| HEADER\nHERE -}\nafter\n4\n" expected = "1\n2\nbefore\nafter\n4\n" dropHeader info sample `shouldBe` expected describe "replaceHeader" $ do it "adds header if there's none present" $ do let config = bHeaderConfig [[re|^before|]] [] info = FileInfo Haskell config Nothing mempty header = "{-| NEWHEADER -}" sample = "1\n2\nbefore\nafter\n4\n" expected = "1\n2\nbefore\n{-| NEWHEADER -}\nafter\n4\n" replaceHeader info header sample `shouldBe` expected it "replaces header if there's existing one" $ do let config = bHeaderConfig [[re|^before|]] [] info = FileInfo Haskell config (Just (3, 4)) mempty header = "{-| NEWHEADER -}" sample = "1\n2\nbefore\n{-| OLD\nHEADER -}\nafter\n4\n" expected = "1\n2\nbefore\n{-| NEWHEADER -}\nafter\n4\n" replaceHeader info header sample `shouldBe` expected describe "extractFileInfo" $ do it "extracts FileInfo from given raw input" $ do let config = bHeaderConfig [] [] meta = Nothing expected = FileInfo Haskell config (Just (1, 13)) (mkVariables [ ( "_haskell_module_copyright" , "(c) Some Guy, 2013\n Someone Else, 2014" ) , ("_haskell_module_license" , "GPL-3") , ("_haskell_module_maintainer" , "sample@email.com") , ("_haskell_module_name" , "Test") , ("_haskell_module_stability" , "experimental") , ("_haskell_module_portability", "POSIX") , ("_haskell_module_longdesc" , "long\ndescription") , ("_haskell_module_shortdesc" , "Short description") ] ) sample <- readFileUtf8 $ samplesDir "haskell" "full.hs" extractFileInfo Haskell config meta sample `shouldBe` expected describe "findHeader" $ do it "finds block header (one line long)" $ do let sample = "\n{-| single line -}\n\n" config = bHeaderConfig [] [] findHeader config sample `shouldBe` Just (1, 1) it "finds line header (one line long)" $ do let sample = "\n-- single line\n\n" config = lHeaderConfig [] [] findHeader config sample `shouldBe` Just (1, 1) it "finds block comment header put after 'putAfter' constraint" $ do let sample = "{-| 1 -}\nfoo\n{-| 2\n2 -}\nbar\n{-| 3\n3 -}" config = bHeaderConfig [[re|^foo|]] [] findHeader config sample `shouldBe` Just (2, 3) it "finds line comment header put after 'putAfter' constraint" $ do let sample = "-- 1\nfoo\n-- 2\n-- 2\nbar\n-- 3\n-- 3" config = lHeaderConfig [[re|^foo|]] [] findHeader config sample `shouldBe` Just (2, 3) it "finds block comment header put after composed constraint" $ do let sample = "{-| 1 -}\nfoo\n{-| 2\n2 -}\nbar\n{-| 3\n3 -}" config = bHeaderConfig [[re|^bar|^foo|]] [] findHeader config sample `shouldBe` Just (5, 6) it "finds line comment header put after composed constraint" $ do let sample = "-- 1\nfoo\n-- 2\n-- 2\nbar\n-- 3\n-- 3" config = lHeaderConfig [[re|^bar|^foo|]] [] findHeader config sample `shouldBe` Just (5, 6) it "finds nothing if no header present" $ do let sample = "some\nrandom\text without header" config = bHeaderConfig [] [] findHeader config sample `shouldBe` Nothing it "finds nothing if header is present before 'putAfter' constraint" $ do let sample = "foo\n{-| 1 -}\nbar\nsome text" config = bHeaderConfig [[re|^bar|]] [] findHeader config sample `shouldBe` Nothing it "correctly detects headers using default YAML configuration" $ do let path = "test-data" "code-samples" defaultConfig' <- parseConfiguration defaultConfig HeadersConfig {..} <- makeHeadersConfig (cLicenseHeaders defaultConfig') sampleC1 <- loadFile $ path "c" "sample1.c" sampleC2 <- loadFile $ path "c" "sample2.c" sampleCpp1 <- loadFile $ path "cpp" "sample1.cpp" sampleCpp2 <- loadFile $ path "cpp" "sample2.cpp" sampleCss1 <- loadFile $ path "css" "sample1.css" sampleCss2 <- loadFile $ path "css" "sample2.css" sampleHs1 <- loadFile $ path "haskell" "sample1.hs" sampleHs2 <- loadFile $ path "haskell" "sample2.hs" sampleHtml1 <- loadFile $ path "html" "sample1.html" sampleHtml2 <- loadFile $ path "html" "sample2.html" sampleJava1 <- loadFile $ path "java" "sample1.java" sampleJava2 <- loadFile $ path "java" "sample2.java" sampleJs1 <- loadFile $ path "js" "sample1.js" sampleRust1 <- loadFile $ path "rust" "sample1.rs" sampleScala1 <- loadFile $ path "scala" "sample1.scala" sampleScala2 <- loadFile $ path "scala" "sample2.scala" sampleShell1 <- loadFile $ path "shell" "sample1.sh" findHeader hscC sampleC1 `shouldBe` Just (1, 3) findHeader hscC sampleC2 `shouldBe` Nothing findHeader hscCpp sampleCpp1 `shouldBe` Just (1, 3) findHeader hscCpp sampleCpp2 `shouldBe` Nothing findHeader hscCss sampleCss1 `shouldBe` Just (1, 4) findHeader hscCss sampleCss2 `shouldBe` Nothing findHeader hscHaskell sampleHs1 `shouldBe` Just (1, 3) findHeader hscHaskell sampleHs2 `shouldBe` Nothing findHeader hscHtml sampleHtml1 `shouldBe` Just (1, 4) findHeader hscHtml sampleHtml2 `shouldBe` Nothing findHeader hscJava sampleJava1 `shouldBe` Just (0, 2) findHeader hscJava sampleJava2 `shouldBe` Nothing findHeader hscJs sampleJs1 `shouldBe` Just (0, 2) findHeader hscRust sampleRust1 `shouldBe` Just (0, 2) findHeader hscScala sampleScala1 `shouldBe` Just (0, 2) findHeader hscScala sampleScala2 `shouldBe` Nothing findHeader hscShell sampleShell1 `shouldBe` Just (2, 3) describe "findBlockHeader" $ do it "finds single line header" $ do let sample = ["", "{-| single line -}", "", ""] findBlockHeader "{-|" "-}" sample 0 `shouldBe` Just (1, 1) it "finds multi line header" $ do let sample = ["", "{-| multi", "line -}", "", ""] findBlockHeader "{-|" "-}" sample 0 `shouldBe` Just (1, 2) it "finds only the first occurence of header" $ do let sample = ["{-| this", "and this -}", "", "{-| no this -}"] findBlockHeader "{-|" "-}" sample 0 `shouldBe` Just (0, 1) it "finds nothing if no header is present" $ do let sample = ["foo", "bar"] findBlockHeader "{-|" "-}" sample 0 `shouldBe` Nothing describe "findLineHeader" $ do it "finds single line header" $ do let sample = ["-- foo", ""] findLineHeader "--" sample 0 `shouldBe` Just (0, 0) it "finds single line header with nothing surrounding it" $ do let sample = ["-- foo"] findLineHeader "--" sample 0 `shouldBe` Just (0, 0) it "finds multi line header with nothing surrounding it" $ do let sample = ["-- 3", "-- 3"] findLineHeader "--" sample 0 `shouldBe` Just (0, 1) it "finds multi line header" $ do let sample = ["", "a", "-- first", "--second", "foo"] findLineHeader "--" sample 0 `shouldBe` Just (2, 3) it "finds only the first occurence of header" $ do let sample = ["a", "-- this one", "-- and this", "", "-- not this"] findLineHeader "--" sample 0 `shouldBe` Just (1, 2) it "finds nothing if no header is present" $ do let sample = ["foo", "bar"] findLineHeader "--" sample 0 `shouldBe` Nothing describe "lastMatching" $ do let patterns = [[re|^foo|]] it "finds very last line that matches given patterns" $ do let sample = ["some text", "hello", "foo bar", "foo baz", "last one"] lastMatching patterns sample `shouldBe` Just 3 it "returns Nothing if no matching input found" $ do let sample = ["some text", "hello", "last one"] lastMatching patterns sample `shouldBe` Nothing it "returns Nothing the input is empty" $ do lastMatching patterns [] `shouldBe` Nothing describe "firstMatching" $ do let patterns = [[re|^foo|]] it "finds very first line that matches given patterns" $ do let sample = ["some text", "hello", "foo bar", "foo baz", "last one"] firstMatching patterns sample `shouldBe` Just 2 it "returns Nothing if the input is empty" $ do firstMatching patterns [] `shouldBe` Nothing describe "splitInput" $ do let sample = "text\n->\nRESULT\n<-\nfoo" fstSplit = [[re|->|]] sndSplit = [[re|<-|]] it "handles empty input and conditions" $ do splitInput [] [] "" `shouldBe` ([], [], []) it "handles input and empty conditions" $ do splitInput [] [] "one\ntwo" `shouldBe` ([], ["one", "two"], []) it "splits input with 1st split condition" $ do let expected = (["text", "->"], ["RESULT", "<-", "foo"], []) splitInput fstSplit [] sample `shouldBe` expected it "splits input with 2nd split condition" $ do let expected = ([], ["text", "->", "RESULT"], ["<-", "foo"]) splitInput [] sndSplit sample `shouldBe` expected it "splits input with both conditions" $ do let expected = (["text", "->"], ["RESULT"], ["<-", "foo"]) splitInput fstSplit sndSplit sample `shouldBe` expected it "splits input when nothing matches the 1st split condition" $ do let expected = ([], ["text", "RESULT", "<-", "foo"], []) splitInput fstSplit [] "text\nRESULT\n<-\nfoo" `shouldBe` expected it "splits input when nothing matches the 2nd split condition" $ do let expected = ([], ["text", "->", "RESULT", "foo"], []) splitInput [] sndSplit "text\n->\nRESULT\nfoo" `shouldBe` expected it "splits input when nothing matches both conditions" $ do let expected = ([], ["text", "RESULT", "foo"], []) splitInput fstSplit sndSplit "text\nRESULT\nfoo" `shouldBe` expected it "handles case when 2nd split is found before 1st split" $ do let expected = ([], ["text"], ["->", "RESULT", "<-", "foo"]) splitInput sndSplit fstSplit sample `shouldBe` expected it "handles case when 1st split is also after 2nd split" $ do let expected = (["foo", "->"], ["RESULT"], ["<-", "bar", "->", "end"]) sample' = "foo\n->\nRESULT\n<-\nbar\n->\nend" splitInput fstSplit sndSplit sample' `shouldBe` expected