tests and docs
This commit is contained in:
parent
c3e1d26ab3
commit
1afe333a4e
7 changed files with 175 additions and 107 deletions
|
@ -22,7 +22,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Nunit" Version="4.2.1" />
|
<PackageReference Include="Nunit" Version="3.14.0" />
|
||||||
<PackageReference Include="Serilog" Version="4.2.0" />
|
<PackageReference Include="Serilog" Version="4.2.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="6.0.0" />
|
||||||
|
|
209
Pentole/path.fs
209
Pentole/path.fs
|
@ -1,33 +1,150 @@
|
||||||
module Pentole.Path
|
namespace Pentole
|
||||||
|
|
||||||
|
open System
|
||||||
|
|
||||||
|
|
||||||
|
module Internal =
|
||||||
let private invalid_chars = System.IO.Path.GetInvalidPathChars ()
|
let private invalid_chars = System.IO.Path.GetInvalidPathChars ()
|
||||||
|
|
||||||
type Path = | Absolute of string | Relative of string
|
let trim (a: string) =
|
||||||
with
|
System.IO.Path.TrimEndingDirectorySeparator a
|
||||||
static member of_string (path: string) =
|
|
||||||
|
let construct (path: string): Result<(string * bool), string> =
|
||||||
let is_absolute (path: string) =
|
let is_absolute (path: string) =
|
||||||
System.IO.Path.IsPathFullyQualified path
|
System.IO.Path.IsPathFullyQualified path
|
||||||
|
|
||||||
|
|
||||||
if System.String.IsNullOrEmpty path then
|
if System.String.IsNullOrEmpty path then
|
||||||
Error "Can't create a path from an empty string"
|
Error "Can't create a path from an empty string"
|
||||||
elif invalid_chars |> Seq.exists (fun c -> path.Contains c) then
|
elif invalid_chars |> Seq.exists (fun c -> path.Contains c) then
|
||||||
Error $"The string contains invalid characters: {path}"
|
Error $"The string contains invalid characters: {path}"
|
||||||
elif is_absolute path then
|
elif is_absolute path then
|
||||||
path
|
path
|
||||||
|> System.IO.Path.TrimEndingDirectorySeparator
|
|> trim
|
||||||
|> Absolute
|
|> fun p -> Ok (p, true)
|
||||||
|> Ok
|
|
||||||
else
|
else
|
||||||
path
|
path
|
||||||
|> System.IO.Path.TrimEndingDirectorySeparator
|
|> trim
|
||||||
|> Relative
|
|> fun p -> Ok (p, false)
|
||||||
|> Ok
|
|
||||||
|
type IPath =
|
||||||
|
abstract member ToString: unit -> string
|
||||||
|
abstract member string_value: string
|
||||||
|
inherit IEquatable<IPath>
|
||||||
|
inherit IComparable
|
||||||
|
|
||||||
|
[<Struct>]
|
||||||
|
[<CustomEquality>]
|
||||||
|
[<CustomComparison>]
|
||||||
|
type AbsolutePath internal (path: string) =
|
||||||
|
interface IPath with
|
||||||
|
member _.ToString () = $"AbsolutePath {path}"
|
||||||
|
member _.string_value = path
|
||||||
|
|
||||||
|
member x.CompareTo (o: obj) =
|
||||||
|
match o with
|
||||||
|
| :? IPath as abs ->
|
||||||
|
let a = abs.string_value
|
||||||
|
a.CompareTo (x :> IPath |> _.string_value)
|
||||||
|
| _ -> 1
|
||||||
|
|
||||||
|
|
||||||
|
interface IEquatable<IPath> with
|
||||||
|
member _.Equals (o: IPath) =
|
||||||
|
Internal.trim o.string_value = path
|
||||||
|
|
||||||
|
[<Struct>]
|
||||||
|
[<CustomEquality>]
|
||||||
|
[<CustomComparison>]
|
||||||
|
type RelativePath internal (path: string) =
|
||||||
|
interface IPath with
|
||||||
|
member _.ToString() = $"RelativePath {path}"
|
||||||
|
member _.string_value = path
|
||||||
|
|
||||||
|
member x.CompareTo (o: obj) =
|
||||||
|
match o with
|
||||||
|
| :? IPath as abs ->
|
||||||
|
let a = abs.string_value
|
||||||
|
a.CompareTo (x :> IPath |> _.string_value)
|
||||||
|
| _ -> 1
|
||||||
|
|
||||||
|
interface IEquatable<IPath> with
|
||||||
|
member _.Equals (o: IPath) =
|
||||||
|
Internal.trim o.string_value = path
|
||||||
|
|
||||||
|
module Path =
|
||||||
|
let of_string (path: string): Result<IPath, string> =
|
||||||
|
match Internal.construct path with
|
||||||
|
| Ok (path, true) -> AbsolutePath path :> IPath |> Ok
|
||||||
|
| Ok (path, false) -> RelativePath path :> IPath |> Ok
|
||||||
|
| Error e -> Error e
|
||||||
|
|
||||||
|
let private should_not_fail (st: string) =
|
||||||
|
st
|
||||||
|
|> of_string
|
||||||
|
|> Result.defaultWith (fun exn -> failwith exn)
|
||||||
|
|
||||||
|
/// Returns the extension of the given path
|
||||||
|
let extension (path: IPath) =
|
||||||
|
path.string_value
|
||||||
|
|> IO.Path.GetExtension
|
||||||
|
|
||||||
|
/// Return the basename of the given path
|
||||||
|
let basename (path: IPath) =
|
||||||
|
path.string_value |> IO.Path.GetFileNameWithoutExtension
|
||||||
|
|
||||||
|
/// Concatenates `path` and `other` and adds a directory separator character between any of the path components if one is not already present.
|
||||||
|
let join (path: IPath) (other: IPath) =
|
||||||
|
(path.string_value, other.string_value)
|
||||||
|
|> System.IO.Path.Join
|
||||||
|
|> should_not_fail
|
||||||
|
|
||||||
|
/// Change the extension of the given path
|
||||||
|
let with_extension (path: IPath) (extension: string) =
|
||||||
|
System.IO.Path.ChangeExtension (path.string_value, extension)
|
||||||
|
|> should_not_fail
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines wheter the path is normalized.
|
||||||
|
/// A normalized path is a path that does not contain any "." or ".." path segments and has no trailing or duplicate slashes
|
||||||
|
/// </summary>
|
||||||
|
let is_normalized (path: IPath) =
|
||||||
|
|
||||||
|
let path: string = path.string_value
|
||||||
|
|
||||||
|
let rec checkSegments = function
|
||||||
|
| [] -> true
|
||||||
|
| ("." | "..")::_ -> false
|
||||||
|
| _::tail -> checkSegments tail
|
||||||
|
|
||||||
|
not (path.Contains "//") && (String.split "/" path |> checkSegments)
|
||||||
|
|
||||||
|
/// Returns the parent directory of the given path.
|
||||||
|
///
|
||||||
|
/// If the path is the root directory or does not have a parent, the original path is returned.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `path`: The path to get the parent directory of.
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
/// The parent directory of the given path, or the original path if it has no parent
|
||||||
|
let parent (path: IPath) =
|
||||||
|
let parent = IO.Path.GetDirectoryName path.string_value
|
||||||
|
if parent = null then
|
||||||
|
path
|
||||||
|
else
|
||||||
|
parent |> should_not_fail
|
||||||
|
|
||||||
|
|
||||||
|
// impure functions
|
||||||
|
// https://docs.python.org/3.8/library/pathlib.html
|
||||||
|
module FileSystem =
|
||||||
|
let resolve (path: IPath) =
|
||||||
|
Native.realpath path.string_value |> Result.bind Path.of_string
|
||||||
|
(*
|
||||||
|
module Pentole.Path
|
||||||
|
|
||||||
|
|
||||||
let private string_apply fun_ = function
|
|
||||||
| Absolute p -> fun_ p |> Absolute
|
|
||||||
| Relative p -> fun_ p |> Relative
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Return the parent path
|
/// Return the parent path
|
||||||
|
@ -36,46 +153,12 @@ let private string_apply fun_ = function
|
||||||
let parent (path: Path) =
|
let parent (path: Path) =
|
||||||
failwith "TODO"
|
failwith "TODO"
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return the extension of the given path
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path"></param>
|
|
||||||
let extension = function
|
|
||||||
| Absolute p ->System.IO.Path.GetExtension p
|
|
||||||
| Relative p ->System.IO.Path.GetExtension p
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Return the basename of the given path
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path"></param>
|
|
||||||
let basename = function
|
|
||||||
| Absolute p -> System.IO.Path.GetFileNameWithoutExtension p
|
|
||||||
| Relative p -> System.IO.Path.GetFileNameWithoutExtension p
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Concatenates `path` and `other` and adds a directory separator character between any of the path components if one is not already present.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path"></param>
|
|
||||||
/// <param name="other"></param>
|
|
||||||
let join (path: Path) (other: Path) =
|
|
||||||
match (path, other) with
|
|
||||||
| (Absolute path | Relative path), (Absolute other| Relative other) ->
|
|
||||||
System.IO.Path.Join (path, other) |> Path.of_string
|
|
||||||
|
|
||||||
let relative_to (parent: Path) (child: Path) =
|
let relative_to (parent: Path) (child: Path) =
|
||||||
match (parent, child) with
|
match (parent, child) with
|
||||||
| (Absolute parent | Relative parent), (Absolute child| Relative child) ->
|
| (Absolute parent | Relative parent), (Absolute child| Relative child) ->
|
||||||
System.IO.Path.GetRelativePath (parent, child)
|
System.IO.Path.GetRelativePath (parent, child)
|
||||||
|> Path.of_string
|
|> Path.of_string
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Change the extension of the given path
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path"></param>
|
|
||||||
/// <param name="extension"></param>
|
|
||||||
let with_extension (path: Path) (extension: string) =
|
|
||||||
path
|
|
||||||
|> string_apply (fun path -> System.IO.Path.ChangeExtension (path, extension))
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Change the basename of the path
|
/// Change the basename of the path
|
||||||
|
@ -86,33 +169,5 @@ let with_name (path: Path) (new_: string) =
|
||||||
failwith "t"
|
failwith "t"
|
||||||
|
|
||||||
|
|
||||||
let (|Nil|Cons|) (arr: 'a array) =
|
|
||||||
match arr.Length with
|
|
||||||
| 0 -> Nil
|
|
||||||
| 1 -> Cons(arr.[0], [||])
|
|
||||||
| _ -> Cons(arr.[0], arr.[1..])
|
|
||||||
|
|
||||||
/// <summary>
|
*)
|
||||||
/// Determines wheter the path is normalized.
|
|
||||||
/// A normalized path is a path that does not contain any "." or ".." path segments and has no trailing or duplicate slashes
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="path"></param>
|
|
||||||
let is_normalized (path_: Path) =
|
|
||||||
|
|
||||||
let path: string = path_ |> function | Absolute a -> a | Relative a -> a
|
|
||||||
|
|
||||||
let rec checkSegments = function
|
|
||||||
| [] -> true
|
|
||||||
| ("." | "..")::_ -> false
|
|
||||||
| _::tail -> checkSegments tail
|
|
||||||
|
|
||||||
not (path.Contains "//") && (String.split "/" path |> checkSegments)
|
|
||||||
|
|
||||||
// impure functions
|
|
||||||
// https://docs.python.org/3.8/library/pathlib.html
|
|
||||||
module FileSystem =
|
|
||||||
let resolve = function
|
|
||||||
| Absolute path ->
|
|
||||||
Native.realpath path |> Result.bind Path.of_string
|
|
||||||
| Relative path ->
|
|
||||||
Native.realpath path |> Result.bind Path.of_string
|
|
||||||
|
|
|
@ -10,9 +10,9 @@ let split(separator: string) (target: string) : string list =
|
||||||
| -1 ->
|
| -1 ->
|
||||||
if start_idx < target.Length then
|
if start_idx < target.Length then
|
||||||
target.Substring(start_idx) :: acc
|
target.Substring(start_idx) :: acc
|
||||||
else
|
else acc
|
||||||
acc
|
|
||||||
| index ->
|
| index ->
|
||||||
let part = target.Substring (start_idx, index - start_idx)
|
let part = target.Substring (start_idx, index - start_idx)
|
||||||
split (part :: acc) (index + slen)
|
split (part :: acc) (index + slen)
|
||||||
split [] 0
|
|
||||||
|
split [] 0 |> List.rev
|
||||||
|
|
|
@ -6,27 +6,34 @@ type Assert with
|
||||||
|
|
||||||
static member inline ok_is_equal<'ok, 'err> (expected: 'ok) (got: Result<'ok, 'err>) =
|
static member inline ok_is_equal<'ok, 'err> (expected: 'ok) (got: Result<'ok, 'err>) =
|
||||||
match got with
|
match got with
|
||||||
| Ok got -> Assert.That (got, Is.EqualTo(expected))
|
| Ok got -> Assert.AreEqual (expected, got)
|
||||||
| Error e -> Assert.Fail $"Expected 'ok, got: {e}"
|
| Error e -> Assert.Fail $"Expected 'ok, got: {e}"
|
||||||
|
|
||||||
static member is_true (got: bool) =
|
static member inline is_true (got: bool) =
|
||||||
Assert.That (got, Is.True)
|
Assert.IsTrue got
|
||||||
|
|
||||||
static member is_false (got: bool) =
|
static member inline is_false (got: bool) =
|
||||||
Assert.That (got, Is.False)
|
Assert.IsFalse got
|
||||||
|
|
||||||
static member ok_is_true (r: Result<bool, 'a>) =
|
static member inline ok_is_true (r: Result<bool, 'a>) =
|
||||||
match r with
|
match r with
|
||||||
| Ok got -> Assert.That (got, Is.True)
|
| Ok got -> Assert.is_true got
|
||||||
| Error e -> Assert.Fail $"Expected truth value, got: {e}"
|
| Error e -> Assert.Fail $"Expected truth value, got: {e}"
|
||||||
|
|
||||||
static member ok_is_false (r: Result<bool, 'e>) =
|
static member inline ok_is_false (r: Result<bool, 'e>) =
|
||||||
match r with
|
match r with
|
||||||
| Ok got -> Assert.That (got, Is.False)
|
| Ok got -> Assert.is_false got
|
||||||
| Error e -> Assert.Fail $"Expected truth value, got: {e}"
|
| Error e -> Assert.Fail $"Expected truth value, got: {e}"
|
||||||
|
|
||||||
static member inline are_equal expected (got: 'a) =
|
static member inline are_equal expected (got: 'a) =
|
||||||
Assert.That (got, Is.EqualTo(expected))
|
Assert.AreEqual (expected, got)
|
||||||
|
|
||||||
static member are_seq_equal (expected: 'a seq) (got: 'a seq) =
|
static member inline are_seq_equal (expected: 'a seq) (got: 'a seq) =
|
||||||
Assert.That (got, Is.EquivalentTo(expected))
|
let s_exp = Seq.map id expected
|
||||||
|
let g_exp = Seq.map id got
|
||||||
|
CollectionAssert.AreEqual (s_exp, g_exp)
|
||||||
|
|
||||||
|
static member inline are_not_equal expected (got: 'a) =
|
||||||
|
Assert.AreNotEqual (expected, got)
|
||||||
|
|
||||||
|
static member inline fail = Assert.Fail
|
||||||
|
|
|
@ -10,11 +10,14 @@ open Pentole.Path
|
||||||
let constructor_test () =
|
let constructor_test () =
|
||||||
"/" |> Path.of_string |> Result.isOk |> Assert.is_true
|
"/" |> Path.of_string |> Result.isOk |> Assert.is_true
|
||||||
'\000' |> string |> Path.of_string |> Result.isOk |> Assert.is_false
|
'\000' |> string |> Path.of_string |> Result.isOk |> Assert.is_false
|
||||||
|
|
||||||
[<Test>]
|
[<Test>]
|
||||||
let absolute_test () =
|
let absolute_test () =
|
||||||
let is_absolute = function
|
|
||||||
| (Absolute path | Relative path) ->
|
|
||||||
System.IO.Path.IsPathFullyQualified path
|
let is_absolute (p: IPath) =
|
||||||
|
p.string_value
|
||||||
|
|> System.IO.Path.IsPathFullyQualified
|
||||||
|
|
||||||
let test (s:string) =
|
let test (s:string) =
|
||||||
s
|
s
|
||||||
|
@ -53,18 +56,22 @@ let resolve_test () =
|
||||||
let equality_test () =
|
let equality_test () =
|
||||||
let p (n: string ) = Path.of_string n |> Result.Unsafe.get
|
let p (n: string ) = Path.of_string n |> Result.Unsafe.get
|
||||||
Assert.are_equal (p "/etc") (p "/etc/")
|
Assert.are_equal (p "/etc") (p "/etc/")
|
||||||
(*
|
Assert.are_not_equal (p "etc") (p "/etc/")
|
||||||
|
Assert.are_not_equal (p "etc") (p "/etc")
|
||||||
|
Assert.are_equal (Path.of_string "/tmp" ) ( Path.of_string "/tmp/")
|
||||||
|
Assert.is_true (Path.of_string "/tmp" = Path.of_string "/tmp/")
|
||||||
|
|
||||||
[<Test>]
|
[<Test>]
|
||||||
let parent_test () =
|
let parent_test () =
|
||||||
let p (n: string ) = Path.of_string n |> Result.get
|
let p (n: string ) = Path.of_string n |> Result.Unsafe.get
|
||||||
let test (s:string) =
|
let test (s:string) =
|
||||||
s
|
s
|
||||||
|> Path.of_string
|
|> Path.of_string
|
||||||
|> Result.get
|
|> Result.Unsafe.get
|
||||||
|> parent
|
|> parent
|
||||||
|
|
||||||
"/" |> test |> Assert.are_equal [p "/"]
|
"/" |> test |> Assert.are_equal (p "/")
|
||||||
"/etc/" |> test |> Assert.are_seq_equal ["/"]
|
"/etc/" |> test |> Assert.are_equal (p "/")
|
||||||
"/etc/conf" |> test |> Assert.are_seq_equal [p "/etc"]
|
"/etc/conf" |> test |> Assert.are_equal (p "/etc")
|
||||||
"/etc/../etc" |> test |> Assert.ok_is_equal (p "/etc")
|
"/etc/../etc" |> test |> Assert.are_equal (p "/etc/../")
|
||||||
*)
|
"etc/../etc" |> test |> Assert.are_equal (p "etc/../")
|
||||||
|
|
|
@ -9,4 +9,3 @@ open Pentole
|
||||||
let split_test () =
|
let split_test () =
|
||||||
let target = "a/b/c"
|
let target = "a/b/c"
|
||||||
Assert.are_seq_equal (target.Split("/")) (String.split "/" target)
|
Assert.are_seq_equal (target.Split("/")) (String.split "/" target)
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
||||||
<PackageReference Include="NUnit" Version="4.2.1" />
|
<PackageReference Include="NUnit" Version="3.14.0" />
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0" />
|
||||||
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
|
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
|
||||||
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
<PackageReference Include="coverlet.collector" Version="6.0.0" />
|
||||||
|
|
Loading…
Reference in a new issue