{- © 2019 Serokell <hi@serokell.io>
 - © 2019 Lars Jellema <lars.jellema@gmail.com>
 -
 - SPDX-License-Identifier: MPL-2.0
 -}

module Nixfmt
    ( errorBundlePretty
    , ParseErrorBundle
    , format
    , formatVerify
    ) where

import Data.Bifunctor (bimap, first)
import Data.Text (Text)
import qualified Text.Megaparsec as Megaparsec (parse)
import Text.Megaparsec.Error (errorBundlePretty)

import Nixfmt.Parser (file)
import Nixfmt.Predoc (layout)
import Nixfmt.Pretty ()
import Nixfmt.Types (ParseErrorBundle)

type Width = Int

-- | @format w filename source@ returns either a parsing error specifying a
-- failure in @filename@ or a formatted version of @source@ with a maximum width
-- of @w@ columns where possible.
format :: Width -> FilePath -> Text -> Either String Text
format :: Width -> FilePath -> Text -> Either FilePath Text
format Width
width FilePath
filename
    = (ParseErrorBundle Text Void -> FilePath)
-> (File -> Text)
-> Either (ParseErrorBundle Text Void) File
-> Either FilePath Text
forall (p :: * -> * -> *) a b c d.
Bifunctor p =>
(a -> b) -> (c -> d) -> p a c -> p b d
bimap ParseErrorBundle Text Void -> FilePath
forall s e.
(VisualStream s, TraversableStream s, ShowErrorComponent e) =>
ParseErrorBundle s e -> FilePath
errorBundlePretty (Width -> File -> Text
forall a. Pretty a => Width -> a -> Text
layout Width
width)
    (Either (ParseErrorBundle Text Void) File -> Either FilePath Text)
-> (Text -> Either (ParseErrorBundle Text Void) File)
-> Text
-> Either FilePath Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Parsec Void Text File
-> FilePath -> Text -> Either (ParseErrorBundle Text Void) File
forall e s a.
Parsec e s a -> FilePath -> s -> Either (ParseErrorBundle s e) a
Megaparsec.parse Parsec Void Text File
file FilePath
filename

formatVerify :: Width -> FilePath -> Text -> Either String Text
formatVerify :: Width -> FilePath -> Text -> Either FilePath Text
formatVerify Width
width FilePath
path Text
unformatted = do
    File
unformattedParsed <- Text -> Either FilePath File
parse Text
unformatted
    let formattedOnce :: Text
formattedOnce = Width -> File -> Text
forall a. Pretty a => Width -> a -> Text
layout Width
width File
unformattedParsed
    File
formattedOnceParsed <- Text -> Either FilePath File
parse Text
formattedOnce
    let formattedTwice :: Text
formattedTwice = Width -> File -> Text
forall a. Pretty a => Width -> a -> Text
layout Width
width File
formattedOnceParsed
    if File
formattedOnceParsed File -> File -> Bool
forall a. Eq a => a -> a -> Bool
/= File
unformattedParsed
    then FilePath -> Either FilePath Text
forall b. FilePath -> Either FilePath b
pleaseReport FilePath
"Parses differently after formatting."
    else if Text
formattedOnce Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
/= Text
formattedTwice
    then FilePath -> Either FilePath Text
forall b. FilePath -> Either FilePath b
pleaseReport FilePath
"Nixfmt is not idempotent."
    else Text -> Either FilePath Text
forall a b. b -> Either a b
Right Text
formattedOnce
    where
        parse :: Text -> Either FilePath File
parse = (ParseErrorBundle Text Void -> FilePath)
-> Either (ParseErrorBundle Text Void) File -> Either FilePath File
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first ParseErrorBundle Text Void -> FilePath
forall s e.
(VisualStream s, TraversableStream s, ShowErrorComponent e) =>
ParseErrorBundle s e -> FilePath
errorBundlePretty (Either (ParseErrorBundle Text Void) File -> Either FilePath File)
-> (Text -> Either (ParseErrorBundle Text Void) File)
-> Text
-> Either FilePath File
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Parsec Void Text File
-> FilePath -> Text -> Either (ParseErrorBundle Text Void) File
forall e s a.
Parsec e s a -> FilePath -> s -> Either (ParseErrorBundle s e) a
Megaparsec.parse Parsec Void Text File
file FilePath
path
        pleaseReport :: FilePath -> Either FilePath b
pleaseReport FilePath
x = FilePath -> Either FilePath b
forall a b. a -> Either a b
Left (FilePath -> Either FilePath b) -> FilePath -> Either FilePath b
forall a b. (a -> b) -> a -> b
$ FilePath
path FilePath -> FilePath -> FilePath
forall a. Semigroup a => a -> a -> a
<> FilePath
": " FilePath -> FilePath -> FilePath
forall a. Semigroup a => a -> a -> a
<> FilePath
x FilePath -> FilePath -> FilePath
forall a. Semigroup a => a -> a -> a
<> FilePath
" This is a bug in nixfmt. Please report it at https://github.com/serokell/nixfmt"