From 7e36fff0f135740181a8967ee2f54172019ec957 Mon Sep 17 00:00:00 2001
From: "Daniel J. Summers"
Date: Thu, 7 Mar 2019 20:31:58 -0600
Subject: [PATCH] Misc util function tweaks
I was going to make wordWrap use Span, but decided against it
---
src/PrayerTracker.Tests/UI/UtilsTests.fs | 84 +++++++++++++-----
src/PrayerTracker.UI/Utils.fs | 107 +++++++++++++----------
2 files changed, 125 insertions(+), 66 deletions(-)
diff --git a/src/PrayerTracker.Tests/UI/UtilsTests.fs b/src/PrayerTracker.Tests/UI/UtilsTests.fs
index 3533049..a1d9f42 100644
--- a/src/PrayerTracker.Tests/UI/UtilsTests.fs
+++ b/src/PrayerTracker.Tests/UI/UtilsTests.fs
@@ -63,26 +63,6 @@ let htmlToPlainTextTests =
}
]
-[]
-let replaceFirstTests =
- testList "replaceFirst" [
- test "replaces the first occurrence when it is found at the beginning of the string" {
- let testString = "unit unit unit"
- Expect.equal (replaceFirst "unit" "test" testString) "test unit unit"
- "First occurrence of a substring was not replaced properly at the beginning of the string"
- }
- test "replaces the first occurrence when it is found in the center of the string" {
- let testString = "test unit test"
- Expect.equal (replaceFirst "unit" "test" testString) "test test test"
- "First occurrence of a substring was not replaced properly when it is in the center of the string"
- }
- test "returns the original string if the replacement isn't found" {
- let testString = "unit tests"
- Expect.equal (replaceFirst "tested" "testing" testString) "unit tests"
- "String which did not have the target substring was not returned properly"
- }
- ]
-
[]
let sndAsStringTests =
testList "sndAsString" [
@@ -91,6 +71,46 @@ let sndAsStringTests =
}
]
+module StringTests =
+
+ open PrayerTracker.Utils.String
+
+ []
+ let replaceFirstTests =
+ testList "String.replaceFirst" [
+ test "replaces the first occurrence when it is found at the beginning of the string" {
+ let testString = "unit unit unit"
+ Expect.equal (replaceFirst "unit" "test" testString) "test unit unit"
+ "First occurrence of a substring was not replaced properly at the beginning of the string"
+ }
+ test "replaces the first occurrence when it is found in the center of the string" {
+ let testString = "test unit test"
+ Expect.equal (replaceFirst "unit" "test" testString) "test test test"
+ "First occurrence of a substring was not replaced properly when it is in the center of the string"
+ }
+ test "returns the original string if the replacement isn't found" {
+ let testString = "unit tests"
+ Expect.equal (replaceFirst "tested" "testing" testString) "unit tests"
+ "String which did not have the target substring was not returned properly"
+ }
+ ]
+
+ []
+ let replaceTests =
+ testList "String.replace" [
+ test "succeeds" {
+ Expect.equal (replace "a" "b" "abacab") "bbbcbb" "String did not replace properly"
+ }
+ ]
+
+ []
+ let trimTests =
+ testList "String.trim" [
+ test "succeeds" {
+ Expect.equal (trim " abc ") "abc" "Space not trimmed from string properly"
+ }
+ ]
+
[]
let stripTagsTests =
let testString = "Here is some text
and some more
"
@@ -127,3 +147,27 @@ let wordWrapTests =
Expect.equal (wordWrap 80 testString) testString "Blank lines were not preserved"
}
]
+
+[]
+let wordWrapBTests =
+ testList "wordWrapB" [
+ test "breaks where it is supposed to" {
+ let testString = "The quick brown fox jumps over the lazy dog\nIt does!"
+ Expect.equal (wordWrap 20 testString) "The quick brown fox\njumps over the lazy\ndog\nIt does!\n"
+ "Line not broken correctly"
+ }
+ test "wraps long line without a space and a line with exact length" {
+ let testString = "Asamatteroffact, the dog does too"
+ Expect.equal (wordWrap 10 testString) "Asamattero\nffact, the\ndog does\ntoo\n"
+ "Longer line not broken correctly"
+ }
+ test "wraps long line without a space and a line with non-exact length" {
+ let testString = "Asamatteroffact, that dog does too"
+ Expect.equal (wordWrap 10 testString) "Asamattero\nffact,\nthat dog\ndoes too\n"
+ "Longer line not broken correctly"
+ }
+ test "preserves blank lines" {
+ let testString = "Here is\n\na string with blank lines"
+ Expect.equal (wordWrap 80 testString) testString "Blank lines were not preserved"
+ }
+ ]
diff --git a/src/PrayerTracker.UI/Utils.fs b/src/PrayerTracker.UI/Utils.fs
index 5449e4d..cc1621e 100644
--- a/src/PrayerTracker.UI/Utils.fs
+++ b/src/PrayerTracker.UI/Utils.fs
@@ -10,29 +10,39 @@ open System
/// Hash a string with a SHA1 hash
let sha1Hash (x : string) =
use alg = SHA1.Create ()
- alg.ComputeHash (ASCIIEncoding().GetBytes x)
+ alg.ComputeHash (Encoding.ASCII.GetBytes x)
|> Seq.map (fun chr -> chr.ToString "x2")
- |> Seq.reduce (+)
+ |> String.concat ""
/// Hash a string using 1,024 rounds of PBKDF2 and a salt
let pbkdf2Hash (salt : Guid) (x : string) =
use alg = new Rfc2898DeriveBytes (x, Encoding.UTF8.GetBytes (salt.ToString "N"), 1024)
- Convert.ToBase64String(alg.GetBytes 64)
+ (alg.GetBytes >> Convert.ToBase64String) 64
-/// Replace the first occurrence of a string with a second string within a given string
-let replaceFirst (needle : string) replacement (haystack : string) =
- let i = haystack.IndexOf needle
- match i with
- | -1 -> haystack
- | _ ->
- seq {
- yield haystack.Substring (0, i)
- yield replacement
- yield haystack.Substring (i + needle.Length)
- }
- |> Seq.reduce (+)
+/// String helper functions
+module String =
+
+ /// string.Trim()
+ let trim (str: string) = str.Trim ()
+
+ /// string.Replace()
+ let replace (find : string) repl (str : string) = str.Replace (find, repl)
+
+
+ /// Replace the first occurrence of a string with a second string within a given string
+ let replaceFirst (needle : string) replacement (haystack : string) =
+ match haystack.IndexOf needle with
+ | -1 -> haystack
+ | idx ->
+ seq {
+ yield haystack.[0..idx - 1]
+ yield replacement
+ yield haystack.[idx + needle.Length..]
+ }
+ |> String.concat ""
+
/// Strip HTML tags from the given string
// Adapted from http://www.dijksterhuis.org/safely-cleaning-html-with-strip_tags-in-csharp/
@@ -51,21 +61,15 @@ let stripTags allowedTags input =
|| htmlTag.IndexOf (sprintf "%s" t) = 0) false
match isAllowed with
| true -> ()
- | false -> output <- replaceFirst tag.Value "" output
+ | false -> output <- String.replaceFirst tag.Value "" output
output
+
/// Wrap a string at the specified number of characters
let wordWrap charPerLine (input : string) =
match input.Length with
| len when len <= charPerLine -> input
| _ ->
- let rec findSpace (inp : string) idx =
- match idx with
- | 0 -> 0
- | _ ->
- match inp.Substring (idx, 1) with
- | null | " " -> idx
- | _ -> findSpace inp (idx - 1)
seq {
for line in input.Replace("\r", "").Split '\n' do
let mutable remaining = line
@@ -73,41 +77,52 @@ let wordWrap charPerLine (input : string) =
| 0 -> ()
| _ ->
while charPerLine < remaining.Length do
- let spaceIdx = findSpace remaining charPerLine
- match spaceIdx with
- | 0 ->
- // No whitespace; just break it at [characters]
- yield remaining.Substring (0, charPerLine)
- remaining <- remaining.Substring charPerLine
- | _ ->
- yield remaining.Substring (0, spaceIdx)
- remaining <- remaining.Substring (spaceIdx + 1)
- match remaining.Length with
- | 0 -> ()
- | _ -> yield remaining
+ match charPerLine + 1 < remaining.Length && remaining.[charPerLine] = ' ' with
+ | true ->
+ // Line length is followed by a space; return [charPerLine] as a line
+ yield remaining.[0..charPerLine - 1]
+ remaining <- remaining.[charPerLine + 1..]
+ | false ->
+ match remaining.[0..charPerLine - 1].LastIndexOf ' ' with
+ | -1 ->
+ // No whitespace; just break it at [characters]
+ yield remaining.[0..charPerLine - 1]
+ remaining <- remaining.[charPerLine..]
+ | spaceIdx ->
+ // Break on the last space in the line
+ yield remaining.[0..spaceIdx - 1]
+ remaining <- remaining.[spaceIdx + 1..]
+ // Leftovers - yum!
+ match remaining.Length with 0 -> () | _ -> yield remaining
}
|> Seq.fold (fun (acc : StringBuilder) line -> acc.AppendFormat ("{0}\n", line)) (StringBuilder ())
|> string
/// Modify the text returned by CKEditor into the format we need for request and announcement text
let ckEditorToText (text : string) =
- text
- .Replace("\n\t", "") // \r
- .Replace(" ", " ")
- .Replace(" ", " ")
- .Replace("
", "
") // \r
- .Replace("
", "")
- .Replace("", "")
- .Trim()
-
+ let trim (str : string) = str.Trim ()
+ [ "\n\t", ""
+ " ", " "
+ " ", " "
+ "
", "
"
+ "
", ""
+ "", ""
+ ]
+ |> List.fold (fun (txt : string) (x, y) -> String.replace x y txt) text
+ |> trim
+
/// Convert an HTML piece of text to plain text
let htmlToPlainText html =
match html with
| null | "" -> ""
| _ ->
- WebUtility.HtmlDecode((html.Trim() |> stripTags [ "br" ]).Replace("
", "\n").Replace("
", "\n"))
- .Replace("\u00a0", " ")
+ html.Trim ()
+ |> stripTags [ "br" ]
+ |> String.replace "
" "\n"
+ |> String.replace "
" "\n"
+ |> WebUtility.HtmlDecode
+ |> String.replace "\u00a0" " "
/// Get the second portion of a tuple as a string
let sndAsString x = (snd >> string) x