{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings #-}

-- | This module provides some standard formats used for correctness across miss.
--   In many cases git only allows specific subsets of bytestrings due to parsing ambiguity
--   or system restrictions.

module Data.Git.Formats (
   SafeString()
 , safeString
 , getSS
 , LfFree
 , lfFree
 , isLfFree
 , getLfFree
 , module System.Filesystem.PathComponent
 ) where

import           Control.Monad.Fail
import qualified Data.ByteString       as B
import           Data.Maybe (fromMaybe)
import           Data.Semigroup
import           Data.String
import           Prelude hiding (fail)
import           System.Filesystem.PathComponent

-- | Strings allowed in email addresses.
newtype SafeString = SS {
      getSS :: B.ByteString -- ^ Get a 'B.ByteString' out of a 'SafeString'
    } deriving (Eq, Ord, Show, Semigroup, Monoid)

instance IsString SafeString where
    fromString = fromMaybe (error "Not a SafeString.") . safeString . fromString

-- | Try to make a 'SafeString'.
safeString :: MonadFail m => B.ByteString -> m SafeString
safeString b | isSafeString b = pure (SS b)
             | otherwise      = fail "Not a valid SafeString."

-- | True when 'safeString' will give back a 'Just'
isSafeString :: B.ByteString -> Bool
isSafeString "" = True
isSafeString b = safe (B.head b) && safe (B.last b) && safeInBetween
    where safe = (`B.notElem` " .,:;<>\"'")
          safeInBetween = B.any (`B.notElem` "<>\x0a\x00") b

-- | The restricted subset of 'B.ByteString's that do not include a linefeed.
newtype LfFree = LfFree {
    getLfFree :: B.ByteString -- ^ Get the 'B.ByteString' out of a 'LfFree'
  } deriving (Eq, Ord, Show, Monoid, Semigroup)

instance IsString LfFree where
  fromString = fromMaybe (error "Not a LfFree, contains a linefeed.") . lfFree . fromString

-- | True when 'lfFree' will result in a Just
isLfFree :: B.ByteString -> Bool
isLfFree = B.notElem 0x0a

-- | Safe constructor for 'LfFree'
lfFree :: MonadFail m => B.ByteString -> m LfFree
lfFree b | isLfFree b = pure (LfFree b)
         | otherwise  = fail "Not a valid LfFree; contains a linefeed."