From 0b6ab12188e7e97a404c800d2c94759814ea0d7b Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Wed, 25 Apr 2018 19:16:50 -0700 Subject: [PATCH 1/2] Add a TrimmedString.trim method This is somewhat similar to `FiniteStringOps.truncate` in that it's guaranteed to turn any `String` into a `TrimmedString`. There are some funny characters that get removed by `String.trim` but don't match on `\s` in regular expressions. I've added an example to demonstrate that these characters can lead to a `String` being "trimmed" a bit more than would technically be necessary to match the `TrimmedString` regex. --- .../eu/timepit/refined/types/string.scala | 25 ++++++++++++++++++- .../refined/types/StringTypesSpec.scala | 5 ++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/modules/core/shared/src/main/scala/eu/timepit/refined/types/string.scala b/modules/core/shared/src/main/scala/eu/timepit/refined/types/string.scala index 2ff71611a..23a003c8d 100644 --- a/modules/core/shared/src/main/scala/eu/timepit/refined/types/string.scala +++ b/modules/core/shared/src/main/scala/eu/timepit/refined/types/string.scala @@ -46,7 +46,30 @@ object string { /** A `String` that contains no leading or trailing whitespace. */ type TrimmedString = String Refined MatchesRegex[W.`"""^(?!\\s).*(? import eu.timepit.refined.types.string._ + * + * scala> TrimmedString.trim(" \n a b c ") + * res0: TrimmedString = a b c + * + * Note that there are some strings can inhabit `TrimmedString` but will + * still be trimmed when passed into this method: + * + * scala> TrimmedString("\u0000a") + * res1: TrimmedString = \u000a + * + * scala> TrimmedString.trim("\u0000a") + * res2: TrimmedString = a + * }}} + */ + def trim(s: String): TrimmedString = Refined.unsafeApply(s.trim) + } /** A `String` representing a hexadecimal number */ type HexStringSpec = MatchesRegex[W.`"""^(([0-9a-f]+)|([0-9A-F]+))$"""`.T] diff --git a/modules/core/shared/src/test/scala/eu/timepit/refined/types/StringTypesSpec.scala b/modules/core/shared/src/test/scala/eu/timepit/refined/types/StringTypesSpec.scala index 101c04522..86c0f2f62 100644 --- a/modules/core/shared/src/test/scala/eu/timepit/refined/types/StringTypesSpec.scala +++ b/modules/core/shared/src/test/scala/eu/timepit/refined/types/StringTypesSpec.scala @@ -35,6 +35,11 @@ class StringTypesSpec extends Properties("StringTypes") { (truncated.value ?= str.take(FString3.maxLength)) } + property("""TrimmedString.trim(str)""") = forAll { (str: String) => + val trimmed = TrimmedString.trim(str) + TrimmedString.from(trimmed.value) ?= Right(trimmed) + } + // Hashes for "" object EmptyString { val md5 = "d41d8cd98f00b204e9800998ecf8427e" From 9a9a877a22b4ad8d49ef346712551c00ebe5c1d9 Mon Sep 17 00:00:00 2001 From: Cody Allen Date: Thu, 26 Apr 2018 07:54:16 -0700 Subject: [PATCH 2/2] Adjust TrimmedString regex This should now match up with the strings that `String.trim` produces. I had to change `.*` to `(?s:.*)` because the line separator character isn't removed by `trim` yet doesn't match `.*` without the `s` flag, so a string like `"\u2028"` can be the result of `trim` but didn't previously match the regular expression. Scalastyle and scalafmt had some competing opinions on how I should format this long regex Witness expression. --- .../eu/timepit/refined/types/string.scala | 21 +++++++++---------- .../refined/types/StringTypesSpec.scala | 4 ++++ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/modules/core/shared/src/main/scala/eu/timepit/refined/types/string.scala b/modules/core/shared/src/main/scala/eu/timepit/refined/types/string.scala index 23a003c8d..7187a1d20 100644 --- a/modules/core/shared/src/main/scala/eu/timepit/refined/types/string.scala +++ b/modules/core/shared/src/main/scala/eu/timepit/refined/types/string.scala @@ -43,8 +43,16 @@ object string { object NonEmptyString extends RefinedTypeOps[NonEmptyString, String] - /** A `String` that contains no leading or trailing whitespace. */ - type TrimmedString = String Refined MatchesRegex[W.`"""^(?!\\s).*(? TrimmedString.trim(" \n a b c ") * res0: TrimmedString = a b c - * - * Note that there are some strings can inhabit `TrimmedString` but will - * still be trimmed when passed into this method: - * - * scala> TrimmedString("\u0000a") - * res1: TrimmedString = \u000a - * - * scala> TrimmedString.trim("\u0000a") - * res2: TrimmedString = a * }}} */ def trim(s: String): TrimmedString = Refined.unsafeApply(s.trim) diff --git a/modules/core/shared/src/test/scala/eu/timepit/refined/types/StringTypesSpec.scala b/modules/core/shared/src/test/scala/eu/timepit/refined/types/StringTypesSpec.scala index 86c0f2f62..7653158e0 100644 --- a/modules/core/shared/src/test/scala/eu/timepit/refined/types/StringTypesSpec.scala +++ b/modules/core/shared/src/test/scala/eu/timepit/refined/types/StringTypesSpec.scala @@ -35,6 +35,10 @@ class StringTypesSpec extends Properties("StringTypes") { (truncated.value ?= str.take(FString3.maxLength)) } + property("""TrimmedString.from(str)""") = forAll { (str: String) => + TrimmedString.from(str).isRight ?= (TrimmedString.trim(str).value == str) + } + property("""TrimmedString.trim(str)""") = forAll { (str: String) => val trimmed = TrimmedString.trim(str) TrimmedString.from(trimmed.value) ?= Right(trimmed)