diff --git a/src/Data/String.purs b/src/Data/String.purs index f927a3b..96fb2a5 100644 --- a/src/Data/String.purs +++ b/src/Data/String.purs @@ -21,8 +21,10 @@ module Data.String , replace , replaceAll , take + , takeRight , takeWhile , drop + , dropRight , dropWhile , stripPrefix , stripSuffix @@ -274,8 +276,8 @@ foreign import _lastIndexOf -> Maybe Int -- | Returns the index of the last occurrence of the pattern in the --- | given string, starting at the specified index --- | and searching backwards towards the beginning of the string. +-- | given string, starting at the specified index +-- | and searching backwards towards the beginning of the string. -- | Returns `Nothing` if there is no match. -- | -- | ```purescript @@ -347,6 +349,15 @@ foreign import replaceAll :: Pattern -> Replacement -> String -> String -- | foreign import take :: Int -> String -> String +-- | Returns the last `n` characters of the string. +-- | +-- | ```purescript +-- | take 5 "Hello World" == "World" +-- | ``` +-- | +takeRight :: Int -> String -> String +takeRight i s = drop (length s - i) s + -- | Returns the string without the first `n` characters. -- | -- | ```purescript @@ -355,6 +366,15 @@ foreign import take :: Int -> String -> String -- | foreign import drop :: Int -> String -> String +-- | Returns the string without the last `n` characters. +-- | +-- | ```purescript +-- | dropRight 6 "Hello World" == "Hello" +-- | ``` +-- | +dropRight :: Int -> String -> String +dropRight i s = take (length s - i) s + -- | Returns the number of contiguous characters at the beginning -- | of the string for which the predicate holds. -- | diff --git a/src/Data/String/NonEmpty.purs b/src/Data/String/NonEmpty.purs new file mode 100644 index 0000000..b3d93a9 --- /dev/null +++ b/src/Data/String/NonEmpty.purs @@ -0,0 +1,489 @@ +-- | Non-empty strings. +-- | +-- | Please note that the examples in this documentation use a notation like +-- | `NonEmptyString "..."` for demonstration purposes, `NonEmptyString` cannot +-- | be created directly like that, as we can't prove the string is non-empty to +-- | the compiler at compile-time. +module Data.String.NonEmpty + ( NonEmptyString + , NonEmptyReplacement(..) + , fromString + , unsafeFromString + , fromCharArray + , singleton + , cons + , snoc + , fromFoldable1 + , toString + , toCharArray + , charAt + , charCodeAt + , toChar + , appendString + , prependString + , contains + , indexOf + , indexOf' + , lastIndexOf + , lastIndexOf' + , uncons + , length + , localeCompare + , replace + , replaceAll + , take + , takeRight + , takeWhile + , drop + , dropRight + , dropWhile + , stripPrefix + , stripSuffix + , count + , splitAt + , toLower + , toUpper + , trim + , joinWith + , join1With + , joinWith1 + , module Data.String + ) where + +import Prelude + +import Data.Foldable (class Foldable) +import Data.Foldable as F +import Data.Maybe (Maybe(..), fromJust) +import Data.Semigroup.Foldable (class Foldable1) +import Data.Semigroup.Foldable as F1 +import Data.String (Pattern(..)) +import Data.String as String +import Data.String.Unsafe as U +import Unsafe.Coerce (unsafeCoerce) + +-- | A string that is known not to be empty. +newtype NonEmptyString = NonEmptyString String + +derive newtype instance eqNonEmptyString ∷ Eq NonEmptyString +derive newtype instance ordNonEmptyString ∷ Ord NonEmptyString +derive newtype instance semigroupNonEmptyString ∷ Semigroup NonEmptyString + +instance showNonEmptyString :: Show NonEmptyString where + show (NonEmptyString s) = "(NonEmptyString.unsafeFromString " <> show s <> ")" + +-- | A newtype used in cases to specify a non-empty replacement for a pattern. +newtype NonEmptyReplacement = NonEmptyReplacement NonEmptyString + +derive newtype instance eqNonEmptyReplacement :: Eq NonEmptyReplacement +derive newtype instance ordNonEmptyReplacement :: Ord NonEmptyReplacement +derive newtype instance semigroupNonEmptyReplacement ∷ Semigroup NonEmptyReplacement + +instance showNonEmptyReplacement :: Show NonEmptyReplacement where + show (NonEmptyReplacement s) = "(NonEmptyReplacement " <> show s <> ")" + +-- | Creates a `NonEmptyString` from a `String`, returning `Nothing` if the +-- | input is empty. +-- | +-- | ```purescript +-- | fromString "" = Nothing +-- | fromString "hello" = Just (NonEmptyString "hello") +-- | ``` +fromString :: String -> Maybe NonEmptyString +fromString = case _ of + "" -> Nothing + s -> Just (NonEmptyString s) + +-- | A partial version of `fromString`. +unsafeFromString :: Partial => String -> NonEmptyString +unsafeFromString = fromJust <<< fromString + +-- | Creates a `NonEmptyString` from a character array `String`, returning +-- | `Nothing` if the input is empty. +-- | +-- | ```purescript +-- | fromCharArray [] = Nothing +-- | fromCharArray ['a', 'b', 'c'] = Just (NonEmptyString "abc") +-- | ``` +fromCharArray :: Array Char -> Maybe NonEmptyString +fromCharArray = case _ of + [] -> Nothing + cs -> Just (NonEmptyString (String.fromCharArray cs)) + +-- | Creates a `NonEmptyString` from a character. +singleton :: Char -> NonEmptyString +singleton = NonEmptyString <<< String.singleton + +-- | Creates a `NonEmptyString` from a string by prepending a character. +-- | +-- | ```purescript +-- | cons 'a' "bc" = NonEmptyString "abc" +-- | cons 'a' "" = NonEmptyString "a" +-- | ``` +cons :: Char -> String -> NonEmptyString +cons c s = NonEmptyString (String.singleton c <> s) + +-- | Creates a `NonEmptyString` from a string by appending a character. +-- | +-- | ```purescript +-- | snoc 'c' "ab" = NonEmptyString "abc" +-- | snoc 'a' "" = NonEmptyString "a" +-- | ``` +snoc :: Char -> String -> NonEmptyString +snoc c s = NonEmptyString (s <> String.singleton c) + +-- | Creates a `NonEmptyString` from a `Foldable1` container carrying +-- | characters. +fromFoldable1 :: forall f. Foldable1 f => f Char -> NonEmptyString +fromFoldable1 = F1.fold1 <<< coe + where + coe ∷ f Char -> f NonEmptyString + coe = unsafeCoerce + +-- | Converts a `NonEmptyString` back into a standard `String`. +toString :: NonEmptyString -> String +toString (NonEmptyString s) = s + +-- | Returns the character at the given index, if the index is within bounds. +-- | +-- | ```purescript +-- | charAt 2 (NonEmptyString "Hello") == Just 'l' +-- | charAt 10 (NonEmptyString "Hello") == Nothing +-- | ``` +charAt :: Int -> NonEmptyString -> Maybe Char +charAt = liftS <<< String.charAt + +-- | Returns the numeric Unicode value of the character at the given index, +-- | if the index is within bounds. +-- | +-- | ```purescript +-- | charCodeAt 2 (NonEmptyString "5 €") == Just 0x20AC +-- | charCodeAt 10 (NonEmptyString "5 €") == Nothing +-- | ``` +charCodeAt :: Int -> NonEmptyString -> Maybe Int +charCodeAt = liftS <<< String.charCodeAt + +-- | Converts the `NonEmptyString` to a character, if the length of the string +-- | is exactly `1`. +-- | +-- | ```purescript +-- | toChar "H" == Just 'H' +-- | toChar "Hi" == Nothing +-- | ``` +toChar :: NonEmptyString -> Maybe Char +toChar (NonEmptyString s) = String.toChar s + +-- | Converts the `NonEmptyString` into an array of characters. +-- | +-- | ```purescript +-- | toCharArray (NonEmptyString "Hello☺\n") == ['H','e','l','l','o','☺','\n'] +-- | ``` +toCharArray :: NonEmptyString -> Array Char +toCharArray (NonEmptyString s) = String.toCharArray s + +-- | Appends a string to this non-empty string. Since one of the strings is +-- | non-empty we know the result will be too. +-- | +-- | ```purescript +-- | appendString (NonEmptyString "Hello") " world" == NonEmptyString "Hello world" +-- | appendString (NonEmptyString "Hello") "" == NonEmptyString "Hello" +-- | ``` +appendString :: NonEmptyString -> String -> NonEmptyString +appendString (NonEmptyString s1) s2 = NonEmptyString (s1 <> s2) + +-- | Prepends a string to this non-empty string. Since one of the strings is +-- | non-empty we know the result will be too. +-- | +-- | ```purescript +-- | prependString "be" (NonEmptyString "fore") == NonEmptyString "before" +-- | prependString "" (NonEmptyString "fore") == NonEmptyString "fore" +-- | ``` +prependString :: String -> NonEmptyString -> NonEmptyString +prependString s1 (NonEmptyString s2) = NonEmptyString (s1 <> s2) + +-- | Returns the first character and the rest of the string. +-- | +-- | ```purescript +-- | uncons "a" == { head: 'a', tail: Nothing } +-- | uncons "Hello World" == { head: 'H', tail: Just (NonEmptyString "ello World") } +-- | ``` +uncons :: NonEmptyString -> { head :: Char, tail :: Maybe NonEmptyString } +uncons (NonEmptyString s) = + { head: U.charAt 0 s + , tail: fromString (String.drop 1 s) + } + +-- | Returns the longest prefix of characters that satisfy the predicate. +-- | `Nothing` is returned if there is no matching prefix. +-- | +-- | ```purescript +-- | takeWhile (_ /= ':') (NonEmptyString "http://purescript.org") == Just (NonEmptyString "http") +-- | takeWhile (_ == 'a') (NonEmptyString "xyz") == Nothing +-- | ``` +takeWhile :: (Char -> Boolean) -> NonEmptyString -> Maybe NonEmptyString +takeWhile f = fromString <<< liftS (String.takeWhile f) + +-- | Returns the suffix remaining after `takeWhile`. +-- | +-- | ```purescript +-- | dropWhile (_ /= '.') (NonEmptyString "Test.purs") == Just (NonEmptyString ".purs") +-- | ``` +dropWhile :: (Char -> Boolean) -> NonEmptyString -> Maybe NonEmptyString +dropWhile f = fromString <<< liftS (String.dropWhile f) + +-- | If the string starts with the given prefix, return the portion of the +-- | string left after removing it. If the prefix does not match or there is no +-- | remainder, the result will be `Nothing`. +-- | +-- | ```purescript +-- | stripPrefix (Pattern "http:") (NonEmptyString "http://purescript.org") == Just (NonEmptyString "//purescript.org") +-- | stripPrefix (Pattern "http:") (NonEmptyString "https://purescript.org") == Nothing +-- | stripPrefix (Pattern "Hello!") (NonEmptyString "Hello!") == Nothing +-- | ``` +stripPrefix :: Pattern -> NonEmptyString -> Maybe NonEmptyString +stripPrefix pat = fromString <=< liftS (String.stripPrefix pat) + +-- | If the string ends with the given suffix, return the portion of the +-- | string left after removing it. If the suffix does not match or there is no +-- | remainder, the result will be `Nothing`. +-- | +-- | ```purescript +-- | stripSuffix (Pattern ".exe") (NonEmptyString "purs.exe") == Just (NonEmptyString "purs") +-- | stripSuffix (Pattern ".exe") (NonEmptyString "purs") == Nothing +-- | stripSuffix (Pattern "Hello!") (NonEmptyString "Hello!") == Nothing +-- | ``` +stripSuffix :: Pattern -> NonEmptyString -> Maybe NonEmptyString +stripSuffix pat = fromString <=< liftS (String.stripSuffix pat) + +-- | Checks whether the pattern appears in the given string. +-- | +-- | ```purescript +-- | contains (Pattern "needle") (NonEmptyString "haystack with needle") == true +-- | contains (Pattern "needle") (NonEmptyString "haystack") == false +-- | ``` +contains :: Pattern -> NonEmptyString -> Boolean +contains = liftS <<< String.contains + +-- | Returns the index of the first occurrence of the pattern in the +-- | given string. Returns `Nothing` if there is no match. +-- | +-- | ```purescript +-- | indexOf (Pattern "c") (NonEmptyString "abcdc") == Just 2 +-- | indexOf (Pattern "c") (NonEmptyString "aaa") == Nothing +-- | ``` +indexOf :: Pattern -> NonEmptyString -> Maybe Int +indexOf = liftS <<< String.indexOf + +-- | Returns the index of the first occurrence of the pattern in the +-- | given string, starting at the specified index. Returns `Nothing` if there is +-- | no match. +-- | +-- | ```purescript +-- | indexOf' (Pattern "a") 2 (NonEmptyString "ababa") == Just 2 +-- | indexOf' (Pattern "a") 3 (NonEmptyString "ababa") == Just 4 +-- | ``` +indexOf' :: Pattern -> Int -> NonEmptyString -> Maybe Int +indexOf' pat = liftS <<< String.indexOf' pat + +-- | Returns the index of the last occurrence of the pattern in the +-- | given string. Returns `Nothing` if there is no match. +-- | +-- | ```purescript +-- | lastIndexOf (Pattern "c") (NonEmptyString "abcdc") == Just 4 +-- | lastIndexOf (Pattern "c") (NonEmptyString "aaa") == Nothing +-- | ``` +lastIndexOf :: Pattern -> NonEmptyString -> Maybe Int +lastIndexOf = liftS <<< String.lastIndexOf + +-- | Returns the index of the last occurrence of the pattern in the +-- | given string, starting at the specified index +-- | and searching backwards towards the beginning of the string. +-- | Returns `Nothing` if there is no match. +-- | +-- | ```purescript +-- | lastIndexOf' (Pattern "a") 1 (NonEmptyString "ababa") == Just 0 +-- | lastIndexOf' (Pattern "a") 3 (NonEmptyString "ababa") == Just 2 +-- | lastIndexOf' (Pattern "a") 4 (NonEmptyString "ababa") == Just 4 +-- | ``` +lastIndexOf' :: Pattern -> Int -> NonEmptyString -> Maybe Int +lastIndexOf' pat = liftS <<< String.lastIndexOf' pat + +-- | Returns the number of characters the string is composed of. +-- | +-- | ```purescript +-- | length (NonEmptyString "Hello World") == 11 +-- | ``` +length :: NonEmptyString -> Int +length (NonEmptyString s) = String.length s + +-- | Compare two strings in a locale-aware fashion. This is in contrast to +-- | the `Ord` instance on `String` which treats strings as arrays of code +-- | units: +-- | +-- | ```purescript +-- | NonEmptyString "ä" `localeCompare` NonEmptyString "b" == LT +-- | NonEmptyString "ä" `compare` NonEmptyString "b" == GT +-- | ``` +localeCompare :: NonEmptyString -> NonEmptyString -> Ordering +localeCompare (NonEmptyString a) (NonEmptyString b) = String.localeCompare a b + +-- | Replaces the first occurence of the pattern with the replacement string. +-- | +-- | ```purescript +-- | replace (Pattern "<=") (NonEmptyReplacement "≤") (NonEmptyString "a <= b <= c") == NonEmptyString "a ≤ b <= c" +-- | ``` +replace :: Pattern -> NonEmptyReplacement -> NonEmptyString -> NonEmptyString +replace pat (NonEmptyReplacement (NonEmptyString rep)) (NonEmptyString s) = + NonEmptyString (String.replace pat (String.Replacement rep) s) + +-- | Replaces all occurences of the pattern with the replacement string. +-- | +-- | ```purescript +-- | replaceAll (Pattern "<=") (NonEmptyReplacement "≤") (NonEmptyString "a <= b <= c") == NonEmptyString "a ≤ b ≤ c" +-- | ``` +replaceAll :: Pattern -> NonEmptyReplacement -> NonEmptyString -> NonEmptyString +replaceAll pat (NonEmptyReplacement (NonEmptyString rep)) (NonEmptyString s) = + NonEmptyString (String.replaceAll pat (String.Replacement rep) s) + +-- | Returns the first `n` characters of the string. Returns `Nothing` if `n` is +-- | less than 1. +-- | +-- | ```purescript +-- | take 5 (NonEmptyString "Hello World") == Just (NonEmptyString "Hello") +-- | take 0 (NonEmptyString "Hello World") == Nothing +-- | ``` +take :: Int -> NonEmptyString -> Maybe NonEmptyString +take i (NonEmptyString s) + | i < 1 = Nothing + | otherwise = Just (NonEmptyString (String.take i s)) + +-- | Returns the last `n` characters of the string. Returns `Nothing` if `n` is +-- | less than 1. +-- | +-- | ```purescript +-- | take 5 (NonEmptyString "Hello World") == Just (NonEmptyString "World") +-- | take 0 (NonEmptyString "Hello World") == Nothing +-- | ``` +takeRight :: Int -> NonEmptyString -> Maybe NonEmptyString +takeRight i (NonEmptyString s) + | i < 1 = Nothing + | otherwise = Just (NonEmptyString (String.takeRight i s)) + +-- | Returns the string without the first `n` characters. Returns `Nothing` if +-- | more characters are dropped than the string is long. +-- | +-- | ```purescript +-- | drop 6 (NonEmptyString "Hello World") == Just (NonEmptyString "World") +-- | drop 20 (NonEmptyString "Hello World") == Nothing +-- | ``` +drop :: Int -> NonEmptyString -> Maybe NonEmptyString +drop i (NonEmptyString s) + | i >= String.length s = Nothing + | otherwise = Just (NonEmptyString (String.drop i s)) + +-- | Returns the string without the last `n` characters. Returns `Nothing` if +-- | more characters are dropped than the string is long. +-- | +-- | ```purescript +-- | dropRight 6 (NonEmptyString "Hello World") == Just (NonEmptyString "Hello") +-- | dropRight 20 (NonEmptyString "Hello World") == Nothing +-- | ``` +dropRight :: Int -> NonEmptyString -> Maybe NonEmptyString +dropRight i (NonEmptyString s) + | i >= String.length s = Nothing + | otherwise = Just (NonEmptyString (String.dropRight i s)) + +-- | Returns the number of contiguous characters at the beginning of the string +-- | for which the predicate holds. +-- | +-- | ```purescript +-- | count (_ /= 'o') (NonEmptyString "Hello World") == 4 +-- | ``` +count :: (Char -> Boolean) -> NonEmptyString -> Int +count = liftS <<< String.count + +-- | Returns the substrings of a split at the given index, if the index is +-- | within bounds. +-- | +-- | ```purescript +-- | splitAt 2 (NonEmptyString "Hello World") == Just { before: Just (NonEmptyString "He"), after: Just (NonEmptyString "llo World") } +-- | splitAt 10 (NonEmptyString "Hi") == Nothing +-- | ``` +splitAt + :: Int + -> NonEmptyString + -> Maybe { before :: Maybe NonEmptyString, after :: Maybe NonEmptyString } +splitAt i (NonEmptyString s) = case String.splitAt i s of + Just { before, after } -> + Just { before: fromString before, after: fromString after } + Nothing -> + Nothing + +-- | Returns the argument converted to lowercase. +-- | +-- | ```purescript +-- | toLower (NonEmptyString "hElLo") == NonEmptyString "hello" +-- | ``` +toLower :: NonEmptyString -> NonEmptyString +toLower (NonEmptyString s) = NonEmptyString (String.toLower s) + +-- | Returns the argument converted to uppercase. +-- | +-- | ```purescript +-- | toUpper (NonEmptyString "Hello") == NonEmptyString "HELLO" +-- | ``` +toUpper :: NonEmptyString -> NonEmptyString +toUpper (NonEmptyString s) = NonEmptyString (String.toUpper s) + +-- | Removes whitespace from the beginning and end of a string, including +-- | [whitespace characters](http://www.ecma-international.org/ecma-262/5.1/#sec-7.2) +-- | and [line terminators](http://www.ecma-international.org/ecma-262/5.1/#sec-7.3). +-- | If the string is entirely made up of whitespace the result will be Nothing. +-- | +-- | ```purescript +-- | trim (NonEmptyString " Hello \n World\n\t ") == Just (NonEmptyString "Hello \n World") +-- | trim (NonEmptyString " \n") == Nothing +-- | ``` +trim :: NonEmptyString -> Maybe NonEmptyString +trim (NonEmptyString s) = fromString (String.trim s) + +-- | Joins the strings in a container together as a new string, inserting the +-- | first argument as separator between them. The result is not guaranteed to +-- | be non-empty. +-- | +-- | ```purescript +-- | joinWith ", " [NonEmptyString "apple", NonEmptyString "banana"] == "apple, banana" +-- | joinWith ", " [] == "" +-- | ``` +joinWith :: forall f. Foldable f => String -> f NonEmptyString -> String +joinWith splice = F.intercalate splice <<< coe + where + coe :: f NonEmptyString -> f String + coe = unsafeCoerce + +-- | Joins non-empty strings in a non-empty container together as a new +-- | non-empty string, inserting a possibly empty string as separator between +-- | them. The result is guaranteed to be non-empty. +-- | +-- | ```purescript +-- | -- array syntax is used for demonstration here, it would need to be a real `Foldable1` +-- | join1With ", " [NonEmptyString "apple", NonEmptyString "banana"] == NonEmptyString "apple, banana" +-- | join1With "" [NonEmptyString "apple", NonEmptyString "banana"] == NonEmptyString "applebanana" +-- | ``` +join1With :: forall f. Foldable1 f => String -> f NonEmptyString -> NonEmptyString +join1With splice = NonEmptyString <<< joinWith splice + +-- | Joins possibly empty strings in a non-empty container together as a new +-- | non-empty string, inserting a non-empty string as a separator between them. +-- | The result is guaranteed to be non-empty. +-- | +-- | ```purescript +-- | -- array syntax is used for demonstration here, it would need to be a real `Foldable1` +-- | joinWith1 (NonEmptyString ", ") ["apple", "banana"] == NonEmptyString "apple, banana" +-- | joinWith1 (NonEmptyString "/") ["a", "b", "", "c", ""] == NonEmptyString "a/b//c/" +-- | ``` +joinWith1 :: forall f. Foldable1 f => NonEmptyString -> f String -> NonEmptyString +joinWith1 (NonEmptyString splice) = NonEmptyString <<< F.intercalate splice + +liftS :: forall r. (String -> r) -> NonEmptyString -> r +liftS f (NonEmptyString s) = f s diff --git a/test/Test/Data/String.purs b/test/Test/Data/String.purs index 94cce8e..fa89fa6 100644 --- a/test/Test/Data/String.purs +++ b/test/Test/Data/String.purs @@ -139,6 +139,13 @@ testString = do assert $ take 3 "ab" == "ab" assert $ take (-1) "ab" == "" + log "takeRight" + assert $ takeRight 0 "ab" == "" + assert $ takeRight 1 "ab" == "b" + assert $ takeRight 2 "ab" == "ab" + assert $ takeRight 3 "ab" == "ab" + assert $ takeRight (-1) "ab" == "" + log "drop" assert $ drop 0 "ab" == "ab" assert $ drop 1 "ab" == "b" @@ -146,6 +153,13 @@ testString = do assert $ drop 3 "ab" == "" assert $ drop (-1) "ab" == "ab" + log "dropRight" + assert $ dropRight 0 "ab" == "ab" + assert $ dropRight 1 "ab" == "a" + assert $ dropRight 2 "ab" == "" + assert $ dropRight 3 "ab" == "" + assert $ dropRight (-1) "ab" == "ab" + log "count" assert $ count (_ == 'a') "" == 0 assert $ count (_ == 'a') "ab" == 1 diff --git a/test/Test/Data/String/NonEmpty.purs b/test/Test/Data/String/NonEmpty.purs new file mode 100644 index 0000000..fc80784 --- /dev/null +++ b/test/Test/Data/String/NonEmpty.purs @@ -0,0 +1,253 @@ +module Test.Data.String.NonEmpty (testNonEmptyString) where + +import Data.String.NonEmpty + +import Control.Monad.Eff (Eff) +import Control.Monad.Eff.Console (CONSOLE, log) +import Data.Array.Partial as AP +import Data.Foldable (class Foldable, foldl) +import Data.Maybe (Maybe(..), fromJust, isNothing, maybe) +import Data.Semigroup.Foldable (class Foldable1, foldMap1Default) +import Partial.Unsafe (unsafePartial) +import Prelude (class Functor, Ordering(..), Unit, append, discard, negate, not, ($), (&&), (/=), (==)) +import Test.Assert (ASSERT, assert) + +testNonEmptyString :: forall eff. Eff (console :: CONSOLE, assert :: ASSERT | eff) Unit +testNonEmptyString = do + log "fromString" + assert $ fromString "" == Nothing + assert $ fromString "hello" == Just (nes "hello") + + log "fromCharArray" + assert $ fromCharArray [] == Nothing + assert $ fromCharArray ['a', 'b'] == Just (nes "ab") + + log "singleton" + assert $ singleton 'a' == nes "a" + + log "cons" + assert $ cons 'a' "bc" == nes "abc" + assert $ cons 'a' "" == nes "a" + + log "snoc" + assert $ snoc 'c' "ab" == nes "abc" + assert $ snoc 'a' "" == nes "a" + + log "fromFoldable1" + assert $ fromFoldable1 (NEA ['a']) == nes "a" + assert $ fromFoldable1 (NEA ['a', 'b', 'c']) == nes "abc" + + log "charAt" + assert $ charAt 0 (nes "a") == Just 'a' + assert $ charAt 1 (nes "a") == Nothing + assert $ charAt 0 (nes "ab") == Just 'a' + assert $ charAt 1 (nes "ab") == Just 'b' + assert $ charAt 2 (nes "ab") == Nothing + assert $ charAt 2 (nes "Hello") == Just 'l' + assert $ charAt 10 (nes "Hello") == Nothing + + log "charCodeAt" + assert $ charCodeAt 0 (nes "a") == Just 97 + assert $ charCodeAt 1 (nes "a") == Nothing + assert $ charCodeAt 0 (nes "ab") == Just 97 + assert $ charCodeAt 1 (nes "ab") == Just 98 + assert $ charCodeAt 2 (nes "ab") == Nothing + assert $ charCodeAt 2 (nes "5 €") == Just 0x20AC + assert $ charCodeAt 10 (nes "5 €") == Nothing + + log "toChar" + assert $ toChar (nes "a") == Just 'a' + assert $ toChar (nes "ab") == Nothing + + log "toCharArray" + assert $ toCharArray (nes "a") == ['a'] + assert $ toCharArray (nes "ab") == ['a', 'b'] + assert $ toCharArray (nes "Hello☺\n") == ['H','e','l','l','o','☺','\n'] + + log "appendString" + assert $ appendString (nes "Hello") " world" == nes "Hello world" + assert $ appendString (nes "Hello") "" == nes "Hello" + + log "prependString" + assert $ prependString "be" (nes "fore") == nes "before" + assert $ prependString "" (nes "fore") == nes "fore" + + log "uncons" + assert + let m = uncons (nes "a") + in m.head == 'a' && m.tail == Nothing + assert $ + let m = uncons (nes "Hello World") + in m.head == 'H' && m.tail == Just (nes "ello World") + + log "takeWhile" + assert $ takeWhile (\c -> true) (nes "abc") == Just (nes "abc") + assert $ takeWhile (\c -> false) (nes "abc") == Nothing + assert $ takeWhile (\c -> c /= 'b') (nes "aabbcc") == Just (nes "aa") + assert $ takeWhile (_ /= ':') (nes "http://purescript.org") == Just (nes "http") + assert $ takeWhile (_ == 'a') (nes "xyz") == Nothing + + log "dropWhile" + assert $ dropWhile (\c -> true) (nes "abc") == Nothing + assert $ dropWhile (\c -> false) (nes "abc") == Just (nes "abc") + assert $ dropWhile (\c -> c /= 'b') (nes "aabbcc") == Just (nes "bbcc") + assert $ dropWhile (_ /= '.') (nes "Test.purs") == Just (nes ".purs") + + log "stripPrefix" + assert $ stripPrefix (Pattern "") (nes "abc") == Just (nes "abc") + assert $ stripPrefix (Pattern "a") (nes "abc") == Just (nes "bc") + assert $ stripPrefix (Pattern "abc") (nes "abc") == Nothing + assert $ stripPrefix (Pattern "!") (nes "abc") == Nothing + assert $ stripPrefix (Pattern "http:") (nes "http://purescript.org") == Just (nes "//purescript.org") + assert $ stripPrefix (Pattern "http:") (nes "https://purescript.org") == Nothing + assert $ stripPrefix (Pattern "Hello!") (nes "Hello!") == Nothing + + log "stripSuffix" + assert $ stripSuffix (Pattern ".exe") (nes "purs.exe") == Just (nes "purs") + assert $ stripSuffix (Pattern ".exe") (nes "purs") == Nothing + assert $ stripSuffix (Pattern "Hello!") (nes "Hello!") == Nothing + + log "contains" + assert $ contains (Pattern "") (nes "abcd") + assert $ contains (Pattern "bc") (nes "abcd") + assert $ not (contains (Pattern "cb") (nes "abcd")) + assert $ contains (Pattern "needle") (nes "haystack with needle") == true + assert $ contains (Pattern "needle") (nes "haystack") == false + + log "indexOf" + assert $ indexOf (Pattern "") (nes "abcd") == Just 0 + assert $ indexOf (Pattern "bc") (nes "abcd") == Just 1 + assert $ indexOf (Pattern "cb") (nes "abcd") == Nothing + + log "indexOf'" + assert $ indexOf' (Pattern "") (-1) (nes "ab") == Nothing + assert $ indexOf' (Pattern "") 0 (nes "ab") == Just 0 + assert $ indexOf' (Pattern "") 1 (nes "ab") == Just 1 + assert $ indexOf' (Pattern "") 2 (nes "ab") == Just 2 + assert $ indexOf' (Pattern "") 3 (nes "ab") == Nothing + assert $ indexOf' (Pattern "bc") 0 (nes "abcd") == Just 1 + assert $ indexOf' (Pattern "bc") 1 (nes "abcd") == Just 1 + assert $ indexOf' (Pattern "bc") 2 (nes "abcd") == Nothing + assert $ indexOf' (Pattern "cb") 0 (nes "abcd") == Nothing + + log "lastIndexOf" + assert $ lastIndexOf (Pattern "") (nes "abcd") == Just 4 + assert $ lastIndexOf (Pattern "bc") (nes "abcd") == Just 1 + assert $ lastIndexOf (Pattern "cb") (nes "abcd") == Nothing + + log "lastIndexOf'" + assert $ lastIndexOf' (Pattern "") (-1) (nes "ab") == Nothing + assert $ lastIndexOf' (Pattern "") 0 (nes "ab") == Just 0 + assert $ lastIndexOf' (Pattern "") 1 (nes "ab") == Just 1 + assert $ lastIndexOf' (Pattern "") 2 (nes "ab") == Just 2 + assert $ lastIndexOf' (Pattern "") 3 (nes "ab") == Nothing + assert $ lastIndexOf' (Pattern "bc") 0 (nes "abcd") == Nothing + assert $ lastIndexOf' (Pattern "bc") 1 (nes "abcd") == Just 1 + assert $ lastIndexOf' (Pattern "bc") 2 (nes "abcd") == Just 1 + assert $ lastIndexOf' (Pattern "cb") 0 (nes "abcd") == Nothing + + log "length" + assert $ length (nes "a") == 1 + assert $ length (nes "ab") == 2 + + log "localeCompare" + assert $ localeCompare (nes "a") (nes "a") == EQ + assert $ localeCompare (nes "a") (nes "b") == LT + assert $ localeCompare (nes "b") (nes "a") == GT + + log "replace" + assert $ replace (Pattern "b") (NonEmptyReplacement (nes "!")) (nes "abc") == nes "a!c" + assert $ replace (Pattern "b") (NonEmptyReplacement (nes "!")) (nes "abbc") == nes "a!bc" + assert $ replace (Pattern "d") (NonEmptyReplacement (nes "!")) (nes "abc") == nes "abc" + + log "replaceAll" + assert $ replaceAll (Pattern "[b]") (NonEmptyReplacement (nes "!")) (nes "a[b]c") == nes "a!c" + assert $ replaceAll (Pattern "[b]") (NonEmptyReplacement (nes "!")) (nes "a[b]c[b]") == nes "a!c!" + assert $ replaceAll (Pattern "x") (NonEmptyReplacement (nes "!")) (nes "abc") == nes "abc" + + log "take" + assert $ take 0 (nes "ab") == Nothing + assert $ take 1 (nes "ab") == Just (nes "a") + assert $ take 2 (nes "ab") == Just (nes "ab") + assert $ take 3 (nes "ab") == Just (nes "ab") + assert $ take (-1) (nes "ab") == Nothing + + log "takeRight" + assert $ takeRight 0 (nes "ab") == Nothing + assert $ takeRight 1 (nes "ab") == Just (nes "b") + assert $ takeRight 2 (nes "ab") == Just (nes "ab") + assert $ takeRight 3 (nes "ab") == Just (nes "ab") + assert $ takeRight (-1) (nes "ab") == Nothing + + log "drop" + assert $ drop 0 (nes "ab") == Just (nes "ab") + assert $ drop 1 (nes "ab") == Just (nes "b") + assert $ drop 2 (nes "ab") == Nothing + assert $ drop 3 (nes "ab") == Nothing + assert $ drop (-1) (nes "ab") == Just (nes "ab") + + log "dropRight" + assert $ dropRight 0 (nes "ab") == Just (nes "ab") + assert $ dropRight 1 (nes "ab") == Just (nes "a") + assert $ dropRight 2 (nes "ab") == Nothing + assert $ dropRight 3 (nes "ab") == Nothing + assert $ dropRight (-1) (nes "ab") == Just (nes "ab") + + log "count" + assert $ count (_ == 'a') (nes "ab") == 1 + assert $ count (_ == 'a') (nes "aaab") == 3 + assert $ count (_ == 'a') (nes "abaa") == 1 + assert $ count (_ == 'c') (nes "abaa") == 0 + + log "splitAt" + let + testSplitAt i str res = + assert $ case splitAt i str of + Nothing -> + isNothing res + Just { before, after } -> + maybe false (\r -> + r.before == before && r.after == after) res + testSplitAt 0 (nes "a") (Just { before: Nothing, after: Just (nes "a") }) + testSplitAt 1 (nes "ab") (Just { before: Just (nes "a"), after: Just (nes "b") }) + testSplitAt 3 (nes "aabcc") (Just { before: Just (nes "aab"), after: Just (nes "cc") }) + testSplitAt (-1) (nes "abc") Nothing + + log "toLower" + assert $ toLower (nes "bAtMaN") == nes "batman" + + log "toUpper" + assert $ toUpper (nes "bAtMaN") == nes "BATMAN" + + log "trim" + assert $ trim (nes " abc ") == Just (nes "abc") + assert $ trim (nes " \n") == Nothing + + log "joinWith" + assert $ joinWith "" [] == "" + assert $ joinWith "" [nes "a", nes "b"] == "ab" + assert $ joinWith "--" [nes "a", nes "b", nes "c"] == "a--b--c" + + log "join1With" + assert $ join1With "" (NEA [nes "a", nes "b"]) == nes "ab" + assert $ join1With "--" (NEA [nes "a", nes "b", nes "c"]) == nes "a--b--c" + assert $ join1With ", " (NEA [nes "apple", nes "banana"]) == nes "apple, banana" + assert $ join1With "" (NEA [nes "apple", nes "banana"]) == nes "applebanana" + + log "joinWith1" + assert $ joinWith1 (nes " ") (NEA ["a", "b"]) == nes "a b" + assert $ joinWith1 (nes "--") (NEA ["a", "b", "c"]) == nes "a--b--c" + assert $ joinWith1 (nes ", ") (NEA ["apple", "banana"]) == nes "apple, banana" + assert $ joinWith1 (nes "/") (NEA ["a", "b", "", "c", ""]) == nes "a/b//c/" + +nes :: String -> NonEmptyString +nes = unsafePartial unsafeFromString + +newtype NEA a = NEA (Array a) + +derive newtype instance functorNEA :: Functor NEA +derive newtype instance foldableNEA :: Foldable NEA + +instance foldable1NEA :: Foldable1 NEA where + foldMap1 a = foldMap1Default a + fold1 (NEA as) = foldl append (unsafePartial AP.head as) (unsafePartial AP.tail as) diff --git a/test/Test/Main.purs b/test/Test/Main.purs index 8260cc6..e81600f 100644 --- a/test/Test/Main.purs +++ b/test/Test/Main.purs @@ -3,21 +3,29 @@ module Test.Main where import Prelude import Control.Monad.Eff (Eff) -import Control.Monad.Eff.Console (CONSOLE) - +import Control.Monad.Eff.Console (CONSOLE, log) import Test.Assert (ASSERT) import Test.Data.Char (testChar) import Test.Data.String (testString) +import Test.Data.String.CaseInsensitive (testCaseInsensitiveString) import Test.Data.String.CodePoints (testStringCodePoints) +import Test.Data.String.NonEmpty (testNonEmptyString) import Test.Data.String.Regex (testStringRegex) import Test.Data.String.Unsafe (testStringUnsafe) -import Test.Data.String.CaseInsensitive (testCaseInsensitiveString) main :: Eff (console :: CONSOLE, assert :: ASSERT) Unit main = do + log "\n--- Data.Char ---\n" testChar + log "\n--- Data.String ---\n" testString + log "\n--- Data.String.CodePoints ---\n" testStringCodePoints + log "\n--- Data.String.Unsafe ---\n" testStringUnsafe + log "\n--- Data.String.Regex ---\n" testStringRegex + log "\n--- Data.String.CaseInsensitive ---\n" testCaseInsensitiveString + log "\n--- Data.String.NonEmpty ---\n" + testNonEmptyString