-- |
-- Copyright: (c) 2019 Lucas David Traverso
-- License: MPL-2.0
-- Maintainer: Lucas David Traverso <lucas6246@gmail.com>
-- Stability: unstable
-- Portability: portable
--
-- Internal module for Key related features
module Conferer.Key.Internal where

import Data.String (IsString(..))
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Function (on)
import Data.List (stripPrefix, isPrefixOf)
import qualified Data.Char as Char
-- | This type is used extensivelly as a way to point into a 'Conferer.Source.Source'
--   and in turn into a 'Conferer.Config.Config'. The intended way to create them is
--   is using 'mkKey'.
--
--   It's a list of alphanumeric words and each 'Conferer.Source.Source' can interpret
--   it as it sees fit.
newtype Key = Key
  { Key -> [Text]
unKey :: [Text]
  } deriving (Key -> Key -> Bool
(Key -> Key -> Bool) -> (Key -> Key -> Bool) -> Eq Key
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Key -> Key -> Bool
$c/= :: Key -> Key -> Bool
== :: Key -> Key -> Bool
$c== :: Key -> Key -> Bool
Eq, Eq Key
Eq Key
-> (Key -> Key -> Ordering)
-> (Key -> Key -> Bool)
-> (Key -> Key -> Bool)
-> (Key -> Key -> Bool)
-> (Key -> Key -> Bool)
-> (Key -> Key -> Key)
-> (Key -> Key -> Key)
-> Ord Key
Key -> Key -> Bool
Key -> Key -> Ordering
Key -> Key -> Key
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: Key -> Key -> Key
$cmin :: Key -> Key -> Key
max :: Key -> Key -> Key
$cmax :: Key -> Key -> Key
>= :: Key -> Key -> Bool
$c>= :: Key -> Key -> Bool
> :: Key -> Key -> Bool
$c> :: Key -> Key -> Bool
<= :: Key -> Key -> Bool
$c<= :: Key -> Key -> Bool
< :: Key -> Key -> Bool
$c< :: Key -> Key -> Bool
compare :: Key -> Key -> Ordering
$ccompare :: Key -> Key -> Ordering
$cp1Ord :: Eq Key
Ord)

instance Show Key where
  show :: Key -> String
show Key
key = String
"\"" String -> ShowS
forall a. [a] -> [a] -> [a]
++ Text -> String
Text.unpack (Key -> Text
keyName Key
key) String -> ShowS
forall a. [a] -> [a] -> [a]
++ String
"\""

instance IsString Key where
  fromString :: String -> Key
fromString = String -> Key
mkKey

-- | Helper function to create 'Key's, this function always works, but
--   since 'Key's reject some string this function transforms the input
--   to provide lawful 'Key's instead of throwing.
--
--   For example:
--
--   > 'mkKey' "sOmE.KEy" == "some.key"
--   > 'mkKey' "1.key" == "1.key"
--   > 'mkKey' "1_thing.key" == "1thing.key"
--   > 'mkKey' "some....key" == "some.key"
--   > 'mkKey' ".." == ""
mkKey :: String -> Key
mkKey :: String -> Key
mkKey String
s =
  [Text] -> Key
Key ([Text] -> Key) -> (String -> [Text]) -> String -> Key
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
  (Text -> Bool) -> [Text] -> [Text]
forall a. (a -> Bool) -> [a] -> [a]
filter (Text -> Text -> Bool
forall a. Eq a => a -> a -> Bool
/= Text
forall a. Monoid a => a
mempty) ([Text] -> [Text]) -> (String -> [Text]) -> String -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
  (Text -> Text) -> [Text] -> [Text]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ((Char -> Bool) -> Text -> Text
Text.filter Char -> Bool
isKeyCharacter (Text -> Text) -> (Text -> Text) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> Text
Text.toLower) ([Text] -> [Text]) -> (String -> [Text]) -> String -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
  (Char -> Bool) -> Text -> [Text]
Text.split (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'.') (Text -> [Text]) -> (String -> Text) -> String -> [Text]
forall b c a. (b -> c) -> (a -> b) -> a -> c
.
  String -> Text
forall a. IsString a => String -> a
fromString
  (String -> Key) -> String -> Key
forall a b. (a -> b) -> a -> b
$ String
s

-- | Same as 'mkKey' but for 'Text'
fromText :: Text -> Key
fromText :: Text -> Key
fromText = String -> Key
mkKey (String -> Key) -> (Text -> String) -> Text -> Key
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
Text.unpack

-- | Collapse a key into a textual representation
keyName :: Key -> Text
keyName :: Key -> Text
keyName = Text -> [Text] -> Text
Text.intercalate Text
"." ([Text] -> Text) -> (Key -> [Text]) -> Key -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Key -> [Text]
unKey

-- | This function tells if a key is a subkey of another key based
--   using key fragments instead of letters as units
--
-- > 'isKeyPrefixOf' "foo" "foo.bar" == True
-- > 'isKeyPrefixOf' "foo" "foo" == True
-- > 'isKeyPrefixOf' "foo" "fooa" == False
isKeyPrefixOf :: Key -> Key -> Bool
isKeyPrefixOf :: Key -> Key -> Bool
isKeyPrefixOf = [Text] -> [Text] -> Bool
forall a. Eq a => [a] -> [a] -> Bool
isPrefixOf ([Text] -> [Text] -> Bool) -> (Key -> [Text]) -> Key -> Key -> Bool
forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
`on` Key -> [Text]
unKey

-- | Given k1 and k2 this function drops k1 as a prefix from k2, if
--   k1 is not a prefix of k2 it returns 'Nothing'
--
-- > 'keyPrefixOf' "foo" "foo.bar" == Just "bar"
-- > 'keyPrefixOf' "foo" "foo" == Just ""
-- > 'keyPrefixOf' "foo" "fooa" == Nothing
-- > 'keyPrefixOf' "" k == Just k
stripKeyPrefix :: Key -> Key -> Maybe Key
stripKeyPrefix :: Key -> Key -> Maybe Key
stripKeyPrefix Key
key1 Key
key2 = [Text] -> Key
Key ([Text] -> Key) -> Maybe [Text] -> Maybe Key
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> ([Text] -> [Text] -> Maybe [Text]
forall a. Eq a => [a] -> [a] -> Maybe [a]
stripPrefix ([Text] -> [Text] -> Maybe [Text])
-> (Key -> [Text]) -> Key -> Key -> Maybe [Text]
forall b c a. (b -> b -> c) -> (a -> b) -> a -> a -> c
`on` Key -> [Text]
unKey) Key
key1 Key
key2

-- | Concatenate two keys
(/.) :: Key -> Key -> Key
Key
parent /. :: Key -> Key -> Key
/. Key
child = [Text] -> Key
Key (Key -> [Text]
unKey Key
parent [Text] -> [Text] -> [Text]
forall a. [a] -> [a] -> [a]
++ Key -> [Text]
unKey Key
child)

-- | Get raw components from a key, usually to do some manipulation
rawKeyComponents :: Key -> [Text]
rawKeyComponents :: Key -> [Text]
rawKeyComponents = Key -> [Text]
unKey

-- | Get first component of a key and the rest of the key
unconsKey :: Key -> Maybe (Text, Key)
unconsKey :: Key -> Maybe (Text, Key)
unconsKey (Key []) = Maybe (Text, Key)
forall a. Maybe a
Nothing
unconsKey (Key (Text
k:[Text]
ks)) = (Text, Key) -> Maybe (Text, Key)
forall a. a -> Maybe a
Just (Text
k, [Text] -> Key
Key [Text]
ks)

-- | Check if a 'Text' is a valid fragment of a 'Key', meaning
-- that each character 'isKeyCharacter'
isValidKeyFragment :: Text -> Bool
isValidKeyFragment :: Text -> Bool
isValidKeyFragment Text
t =
  (Bool -> Bool
not (Bool -> Bool) -> Bool -> Bool
forall a b. (a -> b) -> a -> b
$ Text -> Bool
Text.null Text
t) Bool -> Bool -> Bool
&& (Char -> Bool) -> Text -> Bool
Text.all Char -> Bool
isKeyCharacter Text
t

-- | Checks if the given 'Char' is a valid for a 'Key'.
-- Meaning it is a lower case ascii letter or a number.
isKeyCharacter :: Char -> Bool
isKeyCharacter :: Char -> Bool
isKeyCharacter Char
c =
  Char -> Bool
Char.isAsciiLower Char
c Bool -> Bool -> Bool
|| Char -> Bool
Char.isDigit Char
c