diff --git a/WoofWare.Expect/Diff.fs b/WoofWare.Expect/Diff.fs index 3832575..42575d4 100644 --- a/WoofWare.Expect/Diff.fs +++ b/WoofWare.Expect/Diff.fs @@ -11,45 +11,50 @@ type Position = int /// A Patience diff is composed of a sequence of transformations to get from one string to another. /// This represents those transformations. -type DiffOperation = +type DiffOperation<'line> = /// This line is the same on both sides of the diff. /// On the left, it appears at position posA. On the right, at position posB. - | Match of posA : Position * posB : Position * line : string + | Match of posA : Position * posB : Position * line : 'line /// Delete this line, which is at this position. - | Delete of posA : Position * line : string + | Delete of posA : Position * line : 'line /// Insert this line at the given position. - | Insert of posB : Position * line : string + | Insert of posB : Position * line : 'line + +/// The diff between two line-oriented streams. Normally the generic parameter will be `string`, indicating +/// that the thing being diffed was text. +type Diff'<'line> = private | Diff of DiffOperation<'line> list /// The diff between two line-oriented pieces of text. -type Diff = private | Diff of DiffOperation list +type Diff = Diff' /// A match between positions in two sequences -type internal LineMatch = +type internal LineMatch<'line> = { PosA : Position PosB : Position - Line : string + Line : 'line } /// Result of finding unique lines in a sequence -type internal UniqueLines = +type internal UniqueLines<'line when 'line : comparison> = { /// Map from line content to its position (only for unique lines) - LinePositions : Map + LinePositions : Map<'line, Position> /// All line counts (for verification) - LineCounts : Map + LineCounts : Map<'line, int> } +/// The diff between two line-oriented pieces of text. [] module Diff = /// Find lines that appear exactly once in a sequence - let private findUniqueLines (lines : string array) : UniqueLines = - let positions = Dictionary () - let counts = Dictionary () + let private findUniqueLines (lines : 'line array) : UniqueLines<'line> = + let positions = Dictionary<'line, Position> () + let counts = Dictionary<'line, int> () lines |> Array.iteri (fun i line -> - if counts.ContainsKey (line) then + if counts.ContainsKey line then counts.[line] <- counts.[line] + 1 else counts.[line] <- 1 @@ -70,7 +75,7 @@ module Diff = } /// Find longest increasing subsequence based on B positions - let private longestIncreasingSubsequence (matches : LineMatch array) : LineMatch list = + let private longestIncreasingSubsequence (matches : LineMatch<'line> array) : LineMatch<'line> list = let n = matches.Length if n = 0 then @@ -103,9 +108,8 @@ module Diff = reconstruct endIndex [] - /// Simple Myers diff implementation. You probably want to use `patience` instead, for more human-readable diffs. - let myers (a : string array) (b : string array) : Diff = - let rec diffHelper (i : int) (j : int) (acc : DiffOperation list) = + let private myers' (a : 'line array) (b : 'line array) : DiffOperation<'line> list = + let rec diffHelper (i : int) (j : int) (acc : DiffOperation<'line> list) = match i < a.Length, j < b.Length with | false, false -> List.rev acc | true, false -> @@ -146,11 +150,14 @@ module Diff = // No close match, just delete and insert diffHelper (i + 1) j (Delete (i * 1, a.[i]) :: acc) - diffHelper 0 0 [] |> Diff + diffHelper 0 0 [] + + /// Simple Myers diff implementation. You probably want to use `patience` instead, for more human-readable diffs. + let myers (a : string array) (b : string array) : Diff = myers' a b |> Diff /// Patience diff: a human-readable line-based diff. /// Operates on lines of string; the function `patience` will split on lines for you. - let rec patienceLines (a : string array) (b : string array) : Diff = + let rec patienceLines (a : 'line array) (b : 'line array) : Diff'<'line> = // Handle empty sequences match a.Length, b.Length with | 0, 0 -> [] |> Diff @@ -177,7 +184,7 @@ module Diff = if Set.isEmpty commonUniques then // No unique common lines, fall back to Myers - myers a b + myers' a b |> Diff else // Build matches for unique common lines let matches = @@ -196,7 +203,7 @@ module Diff = let anchorMatches = longestIncreasingSubsequence matches |> List.toArray // Build diff imperatively - let result = ResizeArray () + let result = ResizeArray> () let mutable prevA = 0 let mutable prevB = 0 @@ -244,26 +251,32 @@ module Diff = patienceLines (a.Split '\n') (b.Split '\n') /// Format the diff as a human-readable string, including line numbers at the left. - let formatWithLineNumbers (Diff ops) : string = + let formatWithLineNumbers' (formatter : 'line -> string) (Diff ops) : string = ops |> List.map (fun op -> match op with - | Match (a, b, line) -> sprintf " %3d %3d %s" a b line - | Delete (a, line) -> sprintf "- %3d %s" a line - | Insert (b, line) -> sprintf "+ %3d %s" b line + | Match (a, b, line) -> sprintf " %3d %3d %s" a b (formatter line) + | Delete (a, line) -> sprintf "- %3d %s" a (formatter line) + | Insert (b, line) -> sprintf "+ %3d %s" b (formatter line) + ) + |> String.concat "\n" + + /// Format the diff as a human-readable string, including line numbers at the left. + let formatWithLineNumbers (d : Diff) : string = formatWithLineNumbers' id d + + /// Format the diff as a human-readable string. + let format' (formatter : 'line -> string) (Diff ops) : string = + ops + |> List.map (fun op -> + match op with + | Match (_, _, line) -> " " + (formatter line) + | Delete (_, line) -> "- " + (formatter line) + | Insert (_, line) -> "+ " + (formatter line) ) |> String.concat "\n" /// Format the diff as a human-readable string. - let format (Diff ops) : string = - ops - |> List.map (fun op -> - match op with - | Match (_, _, line) -> sprintf " %s" line - | Delete (_, line) -> sprintf "- %s" line - | Insert (_, line) -> sprintf "+ %s" line - ) - |> String.concat "\n" + let format (ops : Diff) : string = format' id ops /// Compute diff statistics type internal DiffStats = @@ -274,7 +287,7 @@ module Diff = TotalOperations : int } - let internal computeStats (ops : DiffOperation list) : DiffStats = + let internal computeStats (ops : DiffOperation<'a> list) : DiffStats = let counts = ops |> List.fold diff --git a/WoofWare.Expect/SurfaceBaseline.txt b/WoofWare.Expect/SurfaceBaseline.txt index 089baf1..d7d41a1 100644 --- a/WoofWare.Expect/SurfaceBaseline.txt +++ b/WoofWare.Expect/SurfaceBaseline.txt @@ -8,48 +8,50 @@ WoofWare.Expect.CallerInfo inherit obj, implements WoofWare.Expect.CallerInfo Sy WoofWare.Expect.CallerInfo.Equals [method]: (WoofWare.Expect.CallerInfo, System.Collections.IEqualityComparer) -> bool WoofWare.Expect.CompletedSnapshot inherit obj, implements WoofWare.Expect.CompletedSnapshot System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.Expect.CompletedSnapshot System.IComparable, System.IComparable, System.Collections.IStructuralComparable WoofWare.Expect.CompletedSnapshot.Equals [method]: (WoofWare.Expect.CompletedSnapshot, System.Collections.IEqualityComparer) -> bool -WoofWare.Expect.Diff inherit obj, implements WoofWare.Expect.Diff System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.Expect.Diff System.IComparable, System.IComparable, System.Collections.IStructuralComparable -WoofWare.Expect.Diff.Equals [method]: (WoofWare.Expect.Diff, System.Collections.IEqualityComparer) -> bool +WoofWare.Expect.Diff'`1 inherit obj, implements 'line WoofWare.Expect.Diff' System.IEquatable, System.Collections.IStructuralEquatable, 'line WoofWare.Expect.Diff' System.IComparable, System.IComparable, System.Collections.IStructuralComparable +WoofWare.Expect.Diff'`1.Equals [method]: ('line WoofWare.Expect.Diff', System.Collections.IEqualityComparer) -> bool WoofWare.Expect.DiffModule inherit obj -WoofWare.Expect.DiffModule.format [static method]: WoofWare.Expect.Diff -> string -WoofWare.Expect.DiffModule.formatWithLineNumbers [static method]: WoofWare.Expect.Diff -> string -WoofWare.Expect.DiffModule.myers [static method]: string [] -> string [] -> WoofWare.Expect.Diff -WoofWare.Expect.DiffModule.patience [static method]: string -> string -> WoofWare.Expect.Diff -WoofWare.Expect.DiffModule.patienceLines [static method]: string [] -> string [] -> WoofWare.Expect.Diff -WoofWare.Expect.DiffOperation inherit obj, implements WoofWare.Expect.DiffOperation System.IEquatable, System.Collections.IStructuralEquatable, WoofWare.Expect.DiffOperation System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 3 cases -WoofWare.Expect.DiffOperation+Delete inherit WoofWare.Expect.DiffOperation -WoofWare.Expect.DiffOperation+Delete.get_line [method]: unit -> string -WoofWare.Expect.DiffOperation+Delete.get_posA [method]: unit -> int -WoofWare.Expect.DiffOperation+Delete.line [property]: [read-only] string -WoofWare.Expect.DiffOperation+Delete.posA [property]: [read-only] int -WoofWare.Expect.DiffOperation+Insert inherit WoofWare.Expect.DiffOperation -WoofWare.Expect.DiffOperation+Insert.get_line [method]: unit -> string -WoofWare.Expect.DiffOperation+Insert.get_posB [method]: unit -> int -WoofWare.Expect.DiffOperation+Insert.line [property]: [read-only] string -WoofWare.Expect.DiffOperation+Insert.posB [property]: [read-only] int -WoofWare.Expect.DiffOperation+Match inherit WoofWare.Expect.DiffOperation -WoofWare.Expect.DiffOperation+Match.get_line [method]: unit -> string -WoofWare.Expect.DiffOperation+Match.get_posA [method]: unit -> int -WoofWare.Expect.DiffOperation+Match.get_posB [method]: unit -> int -WoofWare.Expect.DiffOperation+Match.line [property]: [read-only] string -WoofWare.Expect.DiffOperation+Match.posA [property]: [read-only] int -WoofWare.Expect.DiffOperation+Match.posB [property]: [read-only] int -WoofWare.Expect.DiffOperation+Tags inherit obj -WoofWare.Expect.DiffOperation+Tags.Delete [static field]: int = 1 -WoofWare.Expect.DiffOperation+Tags.Insert [static field]: int = 2 -WoofWare.Expect.DiffOperation+Tags.Match [static field]: int = 0 -WoofWare.Expect.DiffOperation.Equals [method]: (WoofWare.Expect.DiffOperation, System.Collections.IEqualityComparer) -> bool -WoofWare.Expect.DiffOperation.get_IsDelete [method]: unit -> bool -WoofWare.Expect.DiffOperation.get_IsInsert [method]: unit -> bool -WoofWare.Expect.DiffOperation.get_IsMatch [method]: unit -> bool -WoofWare.Expect.DiffOperation.get_Tag [method]: unit -> int -WoofWare.Expect.DiffOperation.IsDelete [property]: [read-only] bool -WoofWare.Expect.DiffOperation.IsInsert [property]: [read-only] bool -WoofWare.Expect.DiffOperation.IsMatch [property]: [read-only] bool -WoofWare.Expect.DiffOperation.NewDelete [static method]: (int, string) -> WoofWare.Expect.DiffOperation -WoofWare.Expect.DiffOperation.NewInsert [static method]: (int, string) -> WoofWare.Expect.DiffOperation -WoofWare.Expect.DiffOperation.NewMatch [static method]: (int, int, string) -> WoofWare.Expect.DiffOperation -WoofWare.Expect.DiffOperation.Tag [property]: [read-only] int +WoofWare.Expect.DiffModule.format [static method]: string WoofWare.Expect.Diff' -> string +WoofWare.Expect.DiffModule.format' [static method]: ('line -> string) -> 'line WoofWare.Expect.Diff' -> string +WoofWare.Expect.DiffModule.formatWithLineNumbers [static method]: string WoofWare.Expect.Diff' -> string +WoofWare.Expect.DiffModule.formatWithLineNumbers' [static method]: ('line -> string) -> 'line WoofWare.Expect.Diff' -> string +WoofWare.Expect.DiffModule.myers [static method]: string [] -> string [] -> string WoofWare.Expect.Diff' +WoofWare.Expect.DiffModule.patience [static method]: string -> string -> string WoofWare.Expect.Diff' +WoofWare.Expect.DiffModule.patienceLines [static method]: 'line [] -> 'line [] -> 'line WoofWare.Expect.Diff' +WoofWare.Expect.DiffOperation`1 inherit obj, implements 'line WoofWare.Expect.DiffOperation System.IEquatable, System.Collections.IStructuralEquatable, 'line WoofWare.Expect.DiffOperation System.IComparable, System.IComparable, System.Collections.IStructuralComparable - union type with 3 cases +WoofWare.Expect.DiffOperation`1+Delete inherit 'line WoofWare.Expect.DiffOperation +WoofWare.Expect.DiffOperation`1+Delete.get_line [method]: unit -> 'line +WoofWare.Expect.DiffOperation`1+Delete.get_posA [method]: unit -> int +WoofWare.Expect.DiffOperation`1+Delete.line [property]: [read-only] 'line +WoofWare.Expect.DiffOperation`1+Delete.posA [property]: [read-only] int +WoofWare.Expect.DiffOperation`1+Insert inherit 'line WoofWare.Expect.DiffOperation +WoofWare.Expect.DiffOperation`1+Insert.get_line [method]: unit -> 'line +WoofWare.Expect.DiffOperation`1+Insert.get_posB [method]: unit -> int +WoofWare.Expect.DiffOperation`1+Insert.line [property]: [read-only] 'line +WoofWare.Expect.DiffOperation`1+Insert.posB [property]: [read-only] int +WoofWare.Expect.DiffOperation`1+Match inherit 'line WoofWare.Expect.DiffOperation +WoofWare.Expect.DiffOperation`1+Match.get_line [method]: unit -> 'line +WoofWare.Expect.DiffOperation`1+Match.get_posA [method]: unit -> int +WoofWare.Expect.DiffOperation`1+Match.get_posB [method]: unit -> int +WoofWare.Expect.DiffOperation`1+Match.line [property]: [read-only] 'line +WoofWare.Expect.DiffOperation`1+Match.posA [property]: [read-only] int +WoofWare.Expect.DiffOperation`1+Match.posB [property]: [read-only] int +WoofWare.Expect.DiffOperation`1+Tags inherit obj +WoofWare.Expect.DiffOperation`1+Tags.Delete [static field]: int = 1 +WoofWare.Expect.DiffOperation`1+Tags.Insert [static field]: int = 2 +WoofWare.Expect.DiffOperation`1+Tags.Match [static field]: int = 0 +WoofWare.Expect.DiffOperation`1.Equals [method]: ('line WoofWare.Expect.DiffOperation, System.Collections.IEqualityComparer) -> bool +WoofWare.Expect.DiffOperation`1.get_IsDelete [method]: unit -> bool +WoofWare.Expect.DiffOperation`1.get_IsInsert [method]: unit -> bool +WoofWare.Expect.DiffOperation`1.get_IsMatch [method]: unit -> bool +WoofWare.Expect.DiffOperation`1.get_Tag [method]: unit -> int +WoofWare.Expect.DiffOperation`1.IsDelete [property]: [read-only] bool +WoofWare.Expect.DiffOperation`1.IsInsert [property]: [read-only] bool +WoofWare.Expect.DiffOperation`1.IsMatch [property]: [read-only] bool +WoofWare.Expect.DiffOperation`1.NewDelete [static method]: (int, 'line) -> 'line WoofWare.Expect.DiffOperation +WoofWare.Expect.DiffOperation`1.NewInsert [static method]: (int, 'line) -> 'line WoofWare.Expect.DiffOperation +WoofWare.Expect.DiffOperation`1.NewMatch [static method]: (int, int, 'line) -> 'line WoofWare.Expect.DiffOperation +WoofWare.Expect.DiffOperation`1.Tag [property]: [read-only] int WoofWare.Expect.Dot inherit obj WoofWare.Expect.Dot+IFileSystem - interface with 3 member(s) WoofWare.Expect.Dot+IFileSystem.DeleteFile [method]: string -> unit diff --git a/WoofWare.Expect/version.json b/WoofWare.Expect/version.json index 1e0fab9..db929b9 100644 --- a/WoofWare.Expect/version.json +++ b/WoofWare.Expect/version.json @@ -1,5 +1,5 @@ { - "version": "0.6", + "version": "0.7", "publicReleaseRefSpec": [ "^refs/heads/main$" ],