module Scrappy.Grep.Output
  ( formatResult
  , formatResults
  , OutputFormat(..)
  ) where

import Scrappy.Grep.DSL (MatchResult(..))
import Data.List (intercalate)

data OutputFormat
  = FormatGrep      -- file:line:col:match
  | FormatVerbose   -- file:line:col: match (with context)
  | FormatJSON      -- JSON output
  | FormatCount     -- Just count of matches
  deriving (Int -> OutputFormat -> ShowS
[OutputFormat] -> ShowS
OutputFormat -> String
(Int -> OutputFormat -> ShowS)
-> (OutputFormat -> String)
-> ([OutputFormat] -> ShowS)
-> Show OutputFormat
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> OutputFormat -> ShowS
showsPrec :: Int -> OutputFormat -> ShowS
$cshow :: OutputFormat -> String
show :: OutputFormat -> String
$cshowList :: [OutputFormat] -> ShowS
showList :: [OutputFormat] -> ShowS
Show, OutputFormat -> OutputFormat -> Bool
(OutputFormat -> OutputFormat -> Bool)
-> (OutputFormat -> OutputFormat -> Bool) -> Eq OutputFormat
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: OutputFormat -> OutputFormat -> Bool
== :: OutputFormat -> OutputFormat -> Bool
$c/= :: OutputFormat -> OutputFormat -> Bool
/= :: OutputFormat -> OutputFormat -> Bool
Eq)

-- | Format a single match result
formatResult :: OutputFormat -> MatchResult -> String
formatResult :: OutputFormat -> MatchResult -> String
formatResult OutputFormat
fmt MatchResult
mr = case OutputFormat
fmt of
  OutputFormat
FormatGrep ->
    MatchResult -> String
mrFilePath MatchResult
mr String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
":" String -> ShowS
forall a. [a] -> [a] -> [a]
++
    Int -> String
forall a. Show a => a -> String
show (MatchResult -> Int
mrLine MatchResult
mr) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
":" String -> ShowS
forall a. [a] -> [a] -> [a]
++
    Int -> String
forall a. Show a => a -> String
show (MatchResult -> Int
mrCol MatchResult
mr) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
":" String -> ShowS
forall a. [a] -> [a] -> [a]
++
    ShowS
escapeNewlines (MatchResult -> String
mrMatchText MatchResult
mr)

  OutputFormat
FormatVerbose ->
    MatchResult -> String
mrFilePath MatchResult
mr String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
":" String -> ShowS
forall a. [a] -> [a] -> [a]
++
    Int -> String
forall a. Show a => a -> String
show (MatchResult -> Int
mrLine MatchResult
mr) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
":" String -> ShowS
forall a. [a] -> [a] -> [a]
++
    Int -> String
forall a. Show a => a -> String
show (MatchResult -> Int
mrCol MatchResult
mr) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
": " String -> ShowS
forall a. [a] -> [a] -> [a]
++
    MatchResult -> String
mrMatchText MatchResult
mr

  OutputFormat
FormatJSON ->
    String
"{\"file\":\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ ShowS
escapeJSON (MatchResult -> String
mrFilePath MatchResult
mr) String -> ShowS
forall a. [a] -> [a] -> [a]
++
    String
"\",\"line\":" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show (MatchResult -> Int
mrLine MatchResult
mr) String -> ShowS
forall a. [a] -> [a] -> [a]
++
    String
",\"col\":" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show (MatchResult -> Int
mrCol MatchResult
mr) String -> ShowS
forall a. [a] -> [a] -> [a]
++
    String
",\"match\":\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ ShowS
escapeJSON (MatchResult -> String
mrMatchText MatchResult
mr) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"\"}"

  OutputFormat
FormatCount -> String
""

-- | Format multiple results
formatResults :: OutputFormat -> [MatchResult] -> String
formatResults :: OutputFormat -> [MatchResult] -> String
formatResults OutputFormat
FormatCount [MatchResult]
results =
  Int -> String
forall a. Show a => a -> String
show ([MatchResult] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [MatchResult]
results) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
" match" String -> ShowS
forall a. [a] -> [a] -> [a]
++ (if [MatchResult] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [MatchResult]
results Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
1 then String
"" else String
"es")
formatResults OutputFormat
FormatJSON [MatchResult]
results =
  String
"[" String -> ShowS
forall a. [a] -> [a] -> [a]
++ String -> [String] -> String
forall a. [a] -> [[a]] -> [a]
intercalate String
"," ((MatchResult -> String) -> [MatchResult] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (OutputFormat -> MatchResult -> String
formatResult OutputFormat
FormatJSON) [MatchResult]
results) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"]"
formatResults OutputFormat
fmt [MatchResult]
results =
  [String] -> String
unlines ([String] -> String) -> [String] -> String
forall a b. (a -> b) -> a -> b
$ (MatchResult -> String) -> [MatchResult] -> [String]
forall a b. (a -> b) -> [a] -> [b]
map (OutputFormat -> MatchResult -> String
formatResult OutputFormat
fmt) [MatchResult]
results

-- | Escape newlines for single-line output
escapeNewlines :: String -> String
escapeNewlines :: ShowS
escapeNewlines = (Char -> String) -> ShowS
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Char -> String
escape
  where
    escape :: Char -> String
escape Char
'\n' = String
"\\n"
    escape Char
'\t' = String
"\\t"
    escape Char
'\r' = String
"\\r"
    escape Char
c = [Char
c]

-- | Escape for JSON strings
escapeJSON :: String -> String
escapeJSON :: ShowS
escapeJSON = (Char -> String) -> ShowS
forall (t :: * -> *) a b. Foldable t => (a -> [b]) -> t a -> [b]
concatMap Char -> String
escape
  where
    escape :: Char -> String
escape Char
'"' = String
"\\\""
    escape Char
'\\' = String
"\\\\"
    escape Char
'\n' = String
"\\n"
    escape Char
'\t' = String
"\\t"
    escape Char
'\r' = String
"\\r"
    escape Char
c = [Char
c]