From 230c1c9442993d8a2b4b757a1f90a7df431e5c53 Mon Sep 17 00:00:00 2001 From: HLWeil Date: Mon, 29 Jul 2024 18:23:41 +0400 Subject: [PATCH 01/20] rework build project for fable compatability --- build/BasicTasks.fs | 163 +++++++++++++++++++++++++++++++----- build/DocumentationTasks.fs | 2 + build/Helpers.fs | 35 +++++++- build/PackageTasks.fs | 146 ++++++++++++++++++++++---------- build/ProjectInfo.fs | 9 +- build/ReleaseTasks.fs | 84 ++++++++++++++++--- build/TestTasks.fs | 68 ++++++++++++--- build/build.fsproj | 21 +++-- 8 files changed, 426 insertions(+), 102 deletions(-) diff --git a/build/BasicTasks.fs b/build/BasicTasks.fs index a15af69..7a439bc 100644 --- a/build/BasicTasks.fs +++ b/build/BasicTasks.fs @@ -4,13 +4,146 @@ open BlackFox.Fake open Fake.IO open Fake.DotNet open Fake.IO.Globbing.Operators +open Helpers + open ProjectInfo +[] +module Helper = + + open Fake + open Fake.Core + + let createProcess exe arg dir = + CreateProcess.fromRawCommandLine exe arg + |> CreateProcess.withWorkingDirectory dir + |> CreateProcess.ensureExitCode + + module Proc = + + module Parallel = + + open System + + let locker = obj() + + let colors = [| + ConsoleColor.DarkYellow + ConsoleColor.DarkCyan + ConsoleColor.Magenta + ConsoleColor.Blue + ConsoleColor.Cyan + ConsoleColor.DarkMagenta + ConsoleColor.DarkBlue + ConsoleColor.Yellow + |] + + let print color (colored: string) (line: string) = + lock locker + (fun () -> + let currentColor = Console.ForegroundColor + Console.ForegroundColor <- color + Console.Write colored + Console.ForegroundColor <- currentColor + Console.WriteLine line) + + let onStdout index name (line: string) = + let color = colors.[index % colors.Length] + if isNull line then + print color $"{name}: --- END ---" "" + else if String.isNotNullOrEmpty line then + print color $"{name}: " line + + let onStderr name (line: string) = + let color = ConsoleColor.Red + if isNull line |> not then + print color $"{name}: " line + + let redirect (index, (name, createProcess)) = + createProcess + |> CreateProcess.redirectOutputIfNotRedirected + |> CreateProcess.withOutputEvents (onStdout index name) (onStderr name) + + let printStarting indexed = + for (index, (name, c: CreateProcess<_>)) in indexed do + let color = colors.[index % colors.Length] + let wd = + c.WorkingDirectory + |> Option.defaultValue "" + let exe = c.Command.Executable + let args = c.Command.Arguments.ToStartInfo + print color $"{name}: {wd}> {exe} {args}" "" + + let run cs = + cs + |> Seq.toArray + |> Array.indexed + |> fun x -> printStarting x; x + |> Array.map redirect + |> Array.Parallel.map Proc.run + + let dotnet = createProcess "dotnet" + + let node = + let nodePath = + match ProcessUtils.tryFindFileOnPath "node" with + | Some path -> path + | None -> + "node was not found in path. Please install it and make sure it's available from your path. " + + "See https://safe-stack.github.io/docs/quickstart/#install-pre-requisites for more info" + |> failwith + + createProcess nodePath + + let npx = + let npmPath = + match ProcessUtils.tryFindFileOnPath "npx" with + | Some path -> path + | None -> + "npm was not found in path. Please install it and make sure it's available from your path. " + + "See https://safe-stack.github.io/docs/quickstart/#install-pre-requisites for more info" + |> failwith + + createProcess npmPath + + let npm = + let npmPath = + match ProcessUtils.tryFindFileOnPath "npm" with + | Some path -> path + | None -> + "npm was not found in path. Please install it and make sure it's available from your path. " + + "See https://safe-stack.github.io/docs/quickstart/#install-pre-requisites for more info" + |> failwith + + createProcess npmPath + + let python = + if System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Windows) then + Fake.Core.Trace.log "Detected Windows System." + createProcess (__SOURCE_DIRECTORY__.Replace(@"\build",@"\.venv\Scripts\python.exe")) + else + Fake.Core.Trace.log "Detected Unix System." + createProcess (__SOURCE_DIRECTORY__.Replace(@"/build",@"/.venv/bin/python")) + + let run proc arg dir = + proc arg dir + |> Proc.run + |> ignore + + let runParallel processes = + processes + |> Proc.Parallel.run + |> ignore + + + let setPrereleaseTag = BuildTask.create "SetPrereleaseTag" [] { - printfn "Please enter pre-release package suffix" - let suffix = System.Console.ReadLine() - prereleaseSuffix <- suffix - prereleaseTag <- (sprintf "%s-%s" release.NugetVersion suffix) + printfn "Please enter pre-release package suffix option: (a/b/rc)" + let suffixTag = System.Console.ReadLine() |> PreReleaseFlag.fromInput + printfn "Plrease enter pre-release package version number" + let suffixNumber = System.Console.ReadLine() |> int + prereleaseSuffix <- suffixTag + prereleaseSuffixNumber <- suffixNumber isPrerelease <- true } @@ -19,24 +152,12 @@ let clean = BuildTask.create "Clean" [] { ++ "src/**/obj" ++ "tests/**/bin" ++ "tests/**/obj" - ++ "pkg" + ++ "dist" + ++ ProjectInfo.netPkgDir |> Shell.cleanDirs } let build = BuildTask.create "Build" [clean] { - !! "src/**/*.*proj" - -- "src/bin/*" - |> Seq.iter (DotNet.build (fun p -> - let msBuildParams = - {p.MSBuildParams with - Properties = ([ - "AssemblyVersion", assemblyVersion - "AssemblyInformationalVersion", stableVersionTag - ] @ p.MSBuildParams.Properties) - } - { - p with - MSBuildParams = msBuildParams - } -)) -} + solutionFile + |> DotNet.build id +} \ No newline at end of file diff --git a/build/DocumentationTasks.fs b/build/DocumentationTasks.fs index ddc31bf..fdc152e 100644 --- a/build/DocumentationTasks.fs +++ b/build/DocumentationTasks.fs @@ -14,6 +14,7 @@ let buildDocs = BuildTask.create "BuildDocs" [build] { } let buildDocsPrerelease = BuildTask.create "BuildDocsPrerelease" [setPrereleaseTag; build] { + let prereleaseTag = PreReleaseFlag.toNugetTag release.SemVer prereleaseSuffix prereleaseSuffixNumber printfn "building docs with prerelease version %s" prereleaseTag runDotNet (sprintf "fsdocs build --eval --clean --properties Configuration=Release --parameters fsdocs-package-version %s" prereleaseTag) @@ -28,6 +29,7 @@ let watchDocs = BuildTask.create "WatchDocs" [build] { } let watchDocsPrerelease = BuildTask.create "WatchDocsPrerelease" [setPrereleaseTag; build] { + let prereleaseTag = PreReleaseFlag.toNugetTag release.SemVer prereleaseSuffix prereleaseSuffixNumber printfn "watching docs with prerelease version %s" prereleaseTag runDotNet (sprintf "fsdocs watch --eval --clean --properties Configuration=Release --parameters fsdocs-package-version %s" prereleaseTag) diff --git a/build/Helpers.fs b/build/Helpers.fs index 96d3290..b45e010 100644 --- a/build/Helpers.fs +++ b/build/Helpers.fs @@ -25,4 +25,37 @@ let runOrDefault defaultTarget args = 0 with e -> printfn "%A" e - 1 \ No newline at end of file + 1 + + +type PreReleaseFlag = + | Alpha + | Beta + | ReleaseCandidate + + static member fromInput (input: string) = + match input with + | "a" -> Alpha + | "b" -> Beta + | "rc" -> ReleaseCandidate + | _ -> failwith "Invalid input" + + static member toNugetTag (semVer : SemVerInfo) (flag: PreReleaseFlag) (number : int) = + let suffix = + match flag with + | Alpha -> $"alpha.{number}" + | Beta -> $"beta.{number}" + | ReleaseCandidate -> $"rc.{number}" + sprintf "%i.%i.%i-%s" semVer.Major semVer.Minor semVer.Patch suffix + + + static member toNPMTag (semVer : SemVerInfo) (flag: PreReleaseFlag) (number : int) = + PreReleaseFlag.toNugetTag semVer flag number + + static member toPyPITag (semVer : SemVerInfo) (tag: PreReleaseFlag) (number : int) = + let suffix = + match tag with + | Alpha -> $"a{number}" + | Beta -> $"b{number}" + | ReleaseCandidate -> $"rc{number}" + sprintf "%i.%i.%i%s" semVer.Major semVer.Minor semVer.Patch suffix diff --git a/build/PackageTasks.fs b/build/PackageTasks.fs index 527cdd2..8b532d4 100644 --- a/build/PackageTasks.fs +++ b/build/PackageTasks.fs @@ -5,57 +5,111 @@ open ProjectInfo open MessagePrompts open BasicTasks open TestTasks +open Helpers open BlackFox.Fake open Fake.Core open Fake.IO.Globbing.Operators -let pack = BuildTask.create "Pack" [clean; build; runTests] { - if promptYesNo (sprintf "creating stable package with version %s OK?" stableVersionTag ) - then - !! "src/**/*.*proj" - -- "src/bin/*" - |> Seq.iter (Fake.DotNet.DotNet.pack (fun p -> - let msBuildParams = - {p.MSBuildParams with - Properties = ([ - "Version",stableVersionTag - "AssemblyVersion", assemblyVersion - "AssemblyInformationalVersion", stableVersionTag - "PackageReleaseNotes", (release.Notes |> String.concat "\r\n") - ] @ p.MSBuildParams.Properties) - } - { - p with - MSBuildParams = msBuildParams - OutputPath = Some pkgDir +open System.Text.RegularExpressions + +/// https://github.com/Freymaurer/Fake.Extensions.Release#release-notes-in-nuget +let private replaceCommitLink input = + let commitLinkPattern = @"\[\[#[a-z0-9]*\]\(.*\)\] " + Regex.Replace(input,commitLinkPattern,"") + +module BundleDotNet = + let bundle (versionTag : string) (versionSuffix : string option) = + System.IO.Directory.CreateDirectory(ProjectInfo.netPkgDir) |> ignore + !! "src/**/*.*proj" + -- "src/bin/*" + |> Seq.iter (Fake.DotNet.DotNet.pack (fun p -> + let msBuildParams = + {p.MSBuildParams with + Properties = ([ + "Version",versionTag + "PackageReleaseNotes", (ProjectInfo.release.Notes |> List.map replaceCommitLink |> String.toLines ) + ] @ p.MSBuildParams.Properties) } - )) - else failwith "aborted" + { + p with + VersionSuffix = versionSuffix + MSBuildParams = msBuildParams + OutputPath = Some ProjectInfo.netPkgDir + } + )) + +let packDotNet = BuildTask.create "PackDotNet" [clean; build; runTests] { + BundleDotNet.bundle ProjectInfo.stableVersionTag None +} + +let packDotNetPrerelease = BuildTask.create "PackDotNetPrerelease" [setPrereleaseTag; clean; build; runTests] { + let prereleaseTag = PreReleaseFlag.toNugetTag release.SemVer prereleaseSuffix prereleaseSuffixNumber + BundleDotNet.bundle prereleaseTag (Some prereleaseTag) +} + +module BundleJs = + let bundle (versionTag: string) = + Fake.JavaScript.Npm.run "bundlejs" (fun o -> o) + //GenerateIndexJs.DynamicObj_generate ProjectInfo.npmPkgDir + failwith "implement IndexJs generation" + Fake.IO.File.readAsString "build/release_package.json" + |> fun t -> + let t = t.Replace(ProjectInfo.stableVersionTag, versionTag) + Fake.IO.File.writeString false $"{ProjectInfo.npmPkgDir}/package.json" t + + Fake.IO.File.readAsString "README.md" + |> Fake.IO.File.writeString false $"{ProjectInfo.npmPkgDir}/README.md" + + "" // "fable-library.**/**" + |> Fake.IO.File.writeString false $"{ProjectInfo.npmPkgDir}/fable_modules/.npmignore" + + Fake.JavaScript.Npm.exec "pack" (fun o -> + { o with + WorkingDirectory = ProjectInfo.npmPkgDir + }) + +let packJS = BuildTask.create "PackJS" [clean; build; runTests] { + BundleJs.bundle ProjectInfo.stableVersionTag +} + +let packJSPrerelease = BuildTask.create "PackJSPrerelease" [setPrereleaseTag; clean; build; runTests] { + let prereleaseTag = PreReleaseFlag.toNPMTag release.SemVer prereleaseSuffix prereleaseSuffixNumber + BundleJs.bundle prereleaseTag +} + +module BundlePy = + let bundle (versionTag: string) = + + run dotnet $"fable src/DynamicObj -o {ProjectInfo.pyPkgDir}/DynamicObj --lang python" "" + run python "-m poetry install --no-root" ProjectInfo.pyPkgDir + //GenerateIndexPy.DynamicObj_generate (ProjectInfo.pyPkgDir + "/dynamicObj") + failwith "implement IndexJs generation" + Fake.IO.File.readAsString "pyproject.toml" + |> fun t -> + let t = t.Replace(ProjectInfo.stableVersionTag, versionTag) + Fake.IO.File.writeString false $"{ProjectInfo.pyPkgDir}/pyproject.toml" t + + Fake.IO.File.readAsString "README.md" + |> Fake.IO.File.writeString false $"{ProjectInfo.pyPkgDir}/README.md" + + //"" // "fable-library.**/**" + //|> Fake.IO.File.writeString false $"{ProjectInfo.npmPkgDir}/fable_modules/.npmignore" + + run python "-m poetry build" ProjectInfo.pyPkgDir //Remove "-o ." because not compatible with publish + + +let packPy = BuildTask.create "PackPy" [clean; build; runTests] { + BundlePy.bundle ProjectInfo.stableVersionTag + } -let packPrerelease = BuildTask.create "PackPrerelease" [setPrereleaseTag; clean; build; runTests] { - if promptYesNo (sprintf "package tag will be %s OK?" prereleaseTag ) - then - !! "src/**/*.*proj" - -- "src/bin/*" - |> Seq.iter (Fake.DotNet.DotNet.pack (fun p -> - let msBuildParams = - {p.MSBuildParams with - Properties = ([ - "Version", prereleaseTag - "AssemblyVersion", assemblyVersion - "AssemblyInformationalVersion", stableVersionTag - "PackageReleaseNotes", (release.Notes |> String.toLines ) - ] @ p.MSBuildParams.Properties) - } - { - p with - VersionSuffix = Some prereleaseSuffix - OutputPath = Some pkgDir - MSBuildParams = msBuildParams - } - )) - else - failwith "aborted" -} \ No newline at end of file +let packPyPrerelease = BuildTask.create "PackPyPrerelease" [setPrereleaseTag; clean; build; runTests] { + let prereleaseTag = PreReleaseFlag.toPyPITag release.SemVer prereleaseSuffix prereleaseSuffixNumber + BundlePy.bundle prereleaseTag + } + + +let pack = BuildTask.createEmpty "Pack" [packDotNet; packJS; packPy] + +let packPrerelease = BuildTask.createEmpty "PackPrerelease" [packDotNetPrerelease;packJSPrerelease;packPyPrerelease] \ No newline at end of file diff --git a/build/ProjectInfo.fs b/build/ProjectInfo.fs index 0ab6745..91d9cc9 100644 --- a/build/ProjectInfo.fs +++ b/build/ProjectInfo.fs @@ -1,6 +1,7 @@ module ProjectInfo open Fake.Core +open Helpers let project = "DynamicObj" @@ -22,7 +23,9 @@ let gitHome = $"https://github.com/{gitOwner}" let projectRepo = $"https://github.com/{gitOwner}/{project}" -let pkgDir = "pkg" +let netPkgDir = "./dist/net" +let npmPkgDir = "./dist/js" +let pyPkgDir = "./dist/py" let release = ReleaseNotes.load "RELEASE_NOTES.md" @@ -34,8 +37,8 @@ let assemblyVersion = $"{stableVersion.Major}.0.0" let assemblyInformationalVersion = $"{stableVersion.Major}.{stableVersion.Minor}.{stableVersion.Patch}" -let mutable prereleaseSuffix = "" +let mutable prereleaseSuffix = PreReleaseFlag.Alpha -let mutable prereleaseTag = "" +let mutable prereleaseSuffixNumber = 0 let mutable isPrerelease = false \ No newline at end of file diff --git a/build/ReleaseTasks.fs b/build/ReleaseTasks.fs index c292e51..e574d30 100644 --- a/build/ReleaseTasks.fs +++ b/build/ReleaseTasks.fs @@ -6,6 +6,7 @@ open BasicTasks open TestTasks open PackageTasks open DocumentationTasks +open Helpers open BlackFox.Fake open Fake.Core @@ -15,7 +16,7 @@ open Fake.Tools open Fake.IO open Fake.IO.Globbing.Operators -let createTag = BuildTask.create "CreateTag" [clean; build; runTests; pack] { +let createTag = BuildTask.create "CreateTag" [clean; build; runTests; packDotNet] { if promptYesNo (sprintf "tagging branch with %s OK?" stableVersionTag ) then Git.Branches.tag "" stableVersionTag Git.Branches.pushTag "" projectRepo stableVersionTag @@ -23,7 +24,9 @@ let createTag = BuildTask.create "CreateTag" [clean; build; runTests; pack] { failwith "aborted" } -let createPrereleaseTag = BuildTask.create "CreatePrereleaseTag" [setPrereleaseTag; clean; build; runTests; packPrerelease] { +let createPrereleaseTag = BuildTask.create "CreatePrereleaseTag" [setPrereleaseTag; clean; build; runTests; packDotNetPrerelease] { + let prereleaseTag = PreReleaseFlag.toNugetTag release.SemVer prereleaseSuffix prereleaseSuffixNumber + if promptYesNo (sprintf "tagging branch with %s OK?" prereleaseTag ) then Git.Branches.tag "" prereleaseTag Git.Branches.pushTag "" projectRepo prereleaseTag @@ -31,33 +34,91 @@ let createPrereleaseTag = BuildTask.create "CreatePrereleaseTag" [setPrereleaseT failwith "aborted" } - -let publishNuget = BuildTask.create "PublishNuget" [clean; build; runTests; pack] { - let targets = (!! (sprintf "%s/*.*pkg" pkgDir )) +let publishNuget = BuildTask.create "PublishNuget" [clean; build; runTests; packDotNet] { + let targets = (!! (sprintf "%s/*.*pkg" netPkgDir )) for target in targets do printfn "%A" target - let msg = sprintf "release package with version %s?" stableVersionTag + let msg = sprintf "[NUGET] release package with version %s?" stableVersionTag if promptYesNo msg then let source = "https://api.nuget.org/v3/index.json" - let apikey = Environment.environVar "NUGET_KEY_CSB" + let apikey = Environment.environVar "NUGET_KEY" for artifact in targets do let result = DotNet.exec id "nuget" (sprintf "push -s %s -k %s %s --skip-duplicate" source apikey artifact) if not result.OK then failwith "failed to push packages" else failwith "aborted" } -let publishNugetPrerelease = BuildTask.create "PublishNugetPrerelease" [clean; build; runTests; packPrerelease] { - let targets = (!! (sprintf "%s/*.*pkg" pkgDir )) +let publishNugetPrerelease = BuildTask.create "PublishNugetPrerelease" [clean; build; runTests; packDotNetPrerelease] { + let targets = (!! (sprintf "%s/*.*pkg" netPkgDir )) for target in targets do printfn "%A" target - let msg = sprintf "release package with version %s?" prereleaseTag + let prereleaseTag = PreReleaseFlag.toNugetTag release.SemVer prereleaseSuffix prereleaseSuffixNumber + let msg = sprintf "[NUGET] release package with version %s?" prereleaseTag if promptYesNo msg then let source = "https://api.nuget.org/v3/index.json" - let apikey = Environment.environVar "NUGET_KEY_CSB" + let apikey = Environment.environVar "NUGET_KEY" for artifact in targets do let result = DotNet.exec id "nuget" (sprintf "push -s %s -k %s %s --skip-duplicate" source apikey artifact) if not result.OK then failwith "failed to push packages" else failwith "aborted" } +let publishNPM = BuildTask.create "PublishNPM" [clean; build; runTests; packJS] { + let target = + (!! (sprintf "%s/*.tgz" npmPkgDir )) + |> Seq.head + printfn "%A" target + let msg = sprintf "[NPM] release package with version %s?" stableVersionTag + if promptYesNo msg then + let apikey = Environment.environVarOrNone "NPM_KEY" + let otp = if apikey.IsSome then $" --otp {apikey.Value}" else "" + Fake.JavaScript.Npm.exec $"publish {target} --access public{otp}" (fun o -> + { o with + WorkingDirectory = "./dist/js/" + }) + else failwith "aborted" +} + +let publishNPMPrerelease = BuildTask.create "PublishNPMPrerelease" [clean; build; runTests; packJSPrerelease] { + let target = + (!! (sprintf "%s/*.tgz" npmPkgDir )) + |> Seq.head + printfn "%A" target + let prereleaseTag = PreReleaseFlag.toNPMTag release.SemVer prereleaseSuffix prereleaseSuffixNumber + let msg = sprintf "[NPM] release package with version %s?" prereleaseTag + if promptYesNo msg then + let apikey = Environment.environVarOrNone "NPM_KEY" + let otp = if apikey.IsSome then $" --otp {apikey.Value}" else "" + Fake.JavaScript.Npm.exec $"publish {target} --access public --tag next{otp}" (fun o -> + { o with + WorkingDirectory = "./dist/js/" + }) + else failwith "aborted" +} + +let publishPyPi = BuildTask.create "PublishPyPi" [clean; build; runTests; packPy] { + let msg = sprintf "[PyPi] release package with version %s?" stableVersionTag + if promptYesNo msg then + let apikey = Environment.environVarOrNone "PYPI_KEY" + match apikey with + | Some key -> + run python $"-m poetry config pypi-token.pypi {key}" ProjectInfo.pyPkgDir + | None -> () + run python "-m poetry publish" ProjectInfo.pyPkgDir + else failwith "aborted" +} + +let publishPyPiPrerelease = BuildTask.create "PublishPyPiPrerelease" [clean; build; runTests; packPyPrerelease] { + let prereleaseTag = PreReleaseFlag.toPyPITag release.SemVer prereleaseSuffix prereleaseSuffixNumber + let msg = sprintf "[PyPi] release package with version %s?" prereleaseTag + if promptYesNo msg then + let apikey = Environment.environVarOrNone "PYPI_KEY" + match apikey with + | Some key -> + run python $"-m poetry config pypi-token.pypi {key}" ProjectInfo.pyPkgDir + | None -> () + run python "-m poetry publish --build" ProjectInfo.pyPkgDir + else failwith "aborted" +} + let releaseDocs = BuildTask.create "ReleaseDocs" [buildDocs] { let msg = sprintf "release docs for version %s?" stableVersionTag if promptYesNo msg then @@ -72,6 +133,7 @@ let releaseDocs = BuildTask.create "ReleaseDocs" [buildDocs] { } let prereleaseDocs = BuildTask.create "PrereleaseDocs" [buildDocsPrerelease] { + let prereleaseTag = PreReleaseFlag.toPyPITag release.SemVer prereleaseSuffix prereleaseSuffixNumber let msg = sprintf "release docs for version %s?" prereleaseTag if promptYesNo msg then Shell.cleanDir "temp" diff --git a/build/TestTasks.fs b/build/TestTasks.fs index 7055e8b..1cdf128 100644 --- a/build/TestTasks.fs +++ b/build/TestTasks.fs @@ -5,21 +5,65 @@ open Fake.DotNet open ProjectInfo open BasicTasks +open Fake.Core -let runTests = BuildTask.create "RunTests" [clean; build] { - testProjects - |> Seq.iter (fun testProject -> - Fake.DotNet.DotNet.test(fun testParams -> - { - testParams with - Logger = Some "console;verbosity=detailed" - Configuration = DotNet.BuildConfiguration.fromString configuration - NoBuild = true - } - ) testProject - ) +module RunTests = + + + //let runTestsJsNative = BuildTask.create "runTestsJSNative" [clean; build] { + // Trace.traceImportant "Start native JavaScript tests" + // for path in ProjectInfo.jsTestProjects do + // // transpile library for native access + // run dotnet $"fable src/ARCtrl -o {path}/ARCtrl" "" + // GenerateIndexJs.ARCtrl_generate($"{path}/ARCtrl") + // run npx $"mocha {path} --timeout 20000" "" + //} + + let runTestsJs = BuildTask.create "runTestsJS" [clean; build] { + for path in ProjectInfo.testProjects do + // transpile js files from fsharp code + run dotnet $"fable {path} -o {path}/js" "" + // run mocha in target path to execute tests + // "--timeout 20000" is used, because json schema validation takes a bit of time. + run node $"{path}/js/Main.js" "" + } + + //let runTestsPyNative = BuildTask.create "runTestsPyNative" [clean; build] { + // Trace.traceImportant "Start native Python tests" + // for path in ProjectInfo.pyTestProjects do + // // transpile library for native access + // run dotnet $"fable src/ARCtrl -o {path}/ARCtrl --lang python" "" + // GenerateIndexPy.ARCtrl_generate($"{path}/ARCtrl") + // run python $"-m pytest {path}" "" + //} + + let runTestsPy = BuildTask.create "runTestsPy" [clean; build] { + for path in ProjectInfo.testProjects do + //transpile py files from fsharp code + run dotnet $"fable {path} -o {path}/py --lang python" "" + // run pyxpecto in target path to execute tests in python + run python $"{path}/py/main.py" "" + } + + let runTestsDotnet = BuildTask.create "runTestsDotnet" [clean; build] { + testProjects + |> Seq.iter (fun testProject -> + Fake.DotNet.DotNet.test(fun testParams -> + { + testParams with + Logger = Some "console;verbosity=detailed" + Configuration = DotNet.BuildConfiguration.fromString configuration + NoBuild = true + } + ) testProject + ) + } + +let runTests = BuildTask.create "RunTests" [clean; build; RunTests.runTestsJs; (*RunTests.runTestsJsNative; *)RunTests.runTestsPy; (*RunTests.runTestsPyNative; *)RunTests.runTestsDotnet] { + () } + // to do: use this once we have actual tests let runTestsWithCodeCov = BuildTask.create "RunTestsWithCodeCov" [clean; build] { let standardParams = Fake.DotNet.MSBuild.CliArguments.Create () diff --git a/build/build.fsproj b/build/build.fsproj index 4c3e5c1..c836ff6 100644 --- a/build/build.fsproj +++ b/build/build.fsproj @@ -19,14 +19,19 @@ - - - - - - - - + + + + + + + + + + + + + From 165bbe0e422c62dff4d1566cebd1dd8e4d7ac83f Mon Sep 17 00:00:00 2001 From: HLWeil Date: Tue, 30 Jul 2024 17:03:09 +0400 Subject: [PATCH 02/20] split projects into base and immutable --- .../DynamicObj.Immutable.fsproj | 41 ++++++++++ .../ImmutableDynamicObj.fs | 2 +- .../Operators.fs | 0 src/DynamicObj/DynObj.fs | 6 +- src/DynamicObj/DynamicObj.fs | 80 +++++++++---------- src/DynamicObj/DynamicObj.fsproj | 3 +- src/DynamicObj/ReflectionUtils.fs | 80 +++++++++++++------ 7 files changed, 136 insertions(+), 76 deletions(-) create mode 100644 src/DynamicObj.Immutable/DynamicObj.Immutable.fsproj rename src/{DynamicObj => DynamicObj.Immutable}/ImmutableDynamicObj.fs (99%) rename src/{DynamicObj => DynamicObj.Immutable}/Operators.fs (100%) diff --git a/src/DynamicObj.Immutable/DynamicObj.Immutable.fsproj b/src/DynamicObj.Immutable/DynamicObj.Immutable.fsproj new file mode 100644 index 0000000..39fd78e --- /dev/null +++ b/src/DynamicObj.Immutable/DynamicObj.Immutable.fsproj @@ -0,0 +1,41 @@ + + + + netstandard2.0 + true + true + true + true + snupkg + true + ../../key.snk + + + + Timo Mühlhaus, Kevin Schneider, F# open source contributors + F# library supporting Dynamic Objects including inheritance in functional style. + MIT + https://csbiology.github.io/DynamicObj/ + F# FSharp dotnet dynamic object + https://github.com/CSBiology/DynamicObj + git + https://github.com/CSBiology/DynamicObj/blob/main/LICENSE + https://github.com/CSBiology/DynamicObj/blob/main/RELEASE_NOTES.md + + + + + + + + + + + + + + + + + + diff --git a/src/DynamicObj/ImmutableDynamicObj.fs b/src/DynamicObj.Immutable/ImmutableDynamicObj.fs similarity index 99% rename from src/DynamicObj/ImmutableDynamicObj.fs rename to src/DynamicObj.Immutable/ImmutableDynamicObj.fs index b624208..94d4d41 100644 --- a/src/DynamicObj/ImmutableDynamicObj.fs +++ b/src/DynamicObj.Immutable/ImmutableDynamicObj.fs @@ -13,7 +13,7 @@ do() /// Represents an DynamicObj's counterpart /// with immutability enabled only. [)>] -type ImmutableDynamicObj internal (map : Map) = +type ImmutableDynamicObj (map : Map) = let mutable properties = map diff --git a/src/DynamicObj/Operators.fs b/src/DynamicObj.Immutable/Operators.fs similarity index 100% rename from src/DynamicObj/Operators.fs rename to src/DynamicObj.Immutable/Operators.fs diff --git a/src/DynamicObj/DynObj.fs b/src/DynamicObj/DynObj.fs index 6df46f2..04a8053 100644 --- a/src/DynamicObj/DynObj.fs +++ b/src/DynamicObj/DynObj.fs @@ -79,16 +79,14 @@ module DynObj = | None -> () let tryGetValue (dyn:DynamicObj) name = - match dyn.TryGetMember name with - | true,value -> Some value - | _ -> None + dyn.TryGetValue name let remove (dyn:DynamicObj) propName = DynamicObj.Remove (dyn, propName) |> ignore let format (d:DynamicObj) = - let members = d.GetDynamicMemberNames() |> Seq.cast |> List.ofSeq + let members = d.GetDynamicMemberNames() |> List.ofSeq let rec loop (object:DynamicObj) (identationLevel:int) (membersLeft:string list) (acc:string list) = let ident = [for i in 0 .. identationLevel-1 do yield " "] |> String.concat "" diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index 5685e1d..8d585ac 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -1,16 +1,26 @@ namespace DynamicObj -open System.Dynamic +//open System.Dynamic open System.Collections.Generic +open Fable.Core +module Fable = + + [] + let getProperty (o:obj) (propName:string) : 'a = + jsNative + + [] + let getPropertyNames (o:obj) : string seq = + jsNative type DynamicObj internal (dict:Dictionary) = - inherit DynamicObject () - + + let properties = dict//new Dictionary() - member private this.Properties = properties + member this.Properties = properties /// new () = DynamicObj(new Dictionary()) @@ -38,19 +48,9 @@ type DynamicObj internal (dict:Dictionary) = member this.SetValue (name,value) = // private // first check to see if there's a native property to set - match ReflectionUtils.tryGetPropertyInfo this name with - | Some property -> - try - // let t = property.ReflectedType - // t.InvokeMember(name,Reflection.BindingFlags.SetProperty,null,this,[|value|]) |> ignore - - //let tmp = Convert.ChangeType(this, property.ReflectedType) - //let tmp = downcast this : (typeof) - property.SetValue(this, value, null) - with - | :? System.ArgumentException -> raise <| System.ArgumentException("Readonly property - Property set method not found.") - | :? System.NullReferenceException -> raise <| System.NullReferenceException() - + match ReflectionUtils.trySetPropertyValue this name value with + | Some _ -> + () | None -> // Next check the Properties collection for member match properties.TryGetValue name with @@ -64,19 +64,13 @@ type DynamicObj internal (dict:Dictionary) = | false -> properties.Remove(name) - override this.TryGetMember(binder:GetMemberBinder,result:obj byref ) = - match this.TryGetValue binder.Name with - | Some value -> result <- value; true - | None -> false - - - override this.TrySetMember(binder:SetMemberBinder, value:obj) = - this.SetValue(binder.Name,value) - true - /// Returns both instance and dynamic properties when passed true, only dynamic properties otherwise. /// Properties are returned as a key value pair of the member names and the boxed values member this.GetProperties includeInstanceProperties = + #if FABLE_COMPILER + Fable.getPropertyNames this + |> Seq.map (fun name -> new KeyValuePair(name, this.TryGetValue name)) + #else seq [ if includeInstanceProperties then for prop in ReflectionUtils.getPublicProperties (this.GetType()) -> @@ -84,11 +78,11 @@ type DynamicObj internal (dict:Dictionary) = for key in properties.Keys -> new KeyValuePair(key, properties.[key]); ] + #endif - /// Returns both instance and dynamic member names. - /// Important to return both so JSON serialization with Json.NET works. - override this.GetDynamicMemberNames() = - this.GetProperties(true) |> Seq.map (fun pair -> pair.Key) + member this.GetDynamicMemberNames() = + properties.Keys + |> seq /// Operator to access a dynamic member by name static member (?) (lookup:#DynamicObj,name:string) = @@ -100,18 +94,18 @@ type DynamicObj internal (dict:Dictionary) = static member (?<-) (lookup:#DynamicObj,name:string,value:'v) = lookup.SetValue (name,value) - /// Copies all dynamic members of the DynamicObj to the target DynamicObj. - member this.CopyDynamicPropertiesTo(target:#DynamicObj) = - this.GetProperties(false) - |> Seq.iter (fun kv -> - target?(kv.Key) <- kv.Value - ) - - /// Returns a new DynamicObj with only the dynamic properties of the original DynamicObj (sans instance properties). - member this.CopyDynamicProperties() = - let target = DynamicObj() - this.CopyDynamicPropertiesTo(target) - target + ///// Copies all dynamic members of the DynamicObj to the target DynamicObj. + //member this.CopyDynamicPropertiesTo(target:#DynamicObj) = + // this.GetProperties(false) + // |> Seq.iter (fun kv -> + // target?(kv.Key) <- kv.Value + // ) + + ///// Returns a new DynamicObj with only the dynamic properties of the original DynamicObj (sans instance properties). + //member this.CopyDynamicProperties() = + // let target = DynamicObj() + // this.CopyDynamicPropertiesTo(target) + // target static member GetValue (lookup:DynamicObj,name) = lookup.TryGetValue(name).Value diff --git a/src/DynamicObj/DynamicObj.fsproj b/src/DynamicObj/DynamicObj.fsproj index 0d4eb44..80ae690 100644 --- a/src/DynamicObj/DynamicObj.fsproj +++ b/src/DynamicObj/DynamicObj.fsproj @@ -25,14 +25,13 @@ - - + diff --git a/src/DynamicObj/ReflectionUtils.fs b/src/DynamicObj/ReflectionUtils.fs index a81afd2..d34d211 100644 --- a/src/DynamicObj/ReflectionUtils.fs +++ b/src/DynamicObj/ReflectionUtils.fs @@ -1,10 +1,14 @@ namespace DynamicObj +open Fable.Core + module ReflectionUtils = open System open System.Reflection + #if !FABLE_COMPILER + // Gets public properties including interface propterties let getPublicProperties (t:Type) = [| @@ -41,8 +45,18 @@ module ReflectionUtils = getPublicProperties (o.GetType()) |> Array.tryFind (fun n -> n.Name = propName) + #endif + /// Sets property value using reflection + #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT + [] + #endif let trySetPropertyValue (o:obj) (propName:string) (value:obj) = + + #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT + jsNative + #else + match tryGetPropertyInfo o propName with | Some property -> try @@ -52,9 +66,16 @@ module ReflectionUtils = | :? System.ArgumentException -> None | :? System.NullReferenceException -> None | None -> None + #endif /// Gets property value as option using reflection + #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT + [] + #endif let tryGetPropertyValue (o:obj) (propName:string) = + #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT + jsNative + #else try match tryGetPropertyInfo o propName with | Some v -> Some (v.GetValue(o,null)) @@ -62,37 +83,44 @@ module ReflectionUtils = with | :? System.Reflection.TargetInvocationException -> None | :? System.NullReferenceException -> None + #endif - /// Gets property value as 'a option using reflection. Cast to 'a - let tryGetPropertyValueAs<'a> (o:obj) (propName:string) = - try - match tryGetPropertyInfo o propName with - | Some v -> Some (v.GetValue(o,null) :?> 'a) - | None -> None - with - | :? System.Reflection.TargetInvocationException -> None - | :? System.NullReferenceException -> None + ///// Gets property value as 'a option using reflection. Cast to 'a + //let tryGetPropertyValueAs<'a> (o:obj) (propName:string) = + // try + // tryGetPropertyValue o propName + // |> Option.map (fun v -> v :?> 'a) + + // with + // | :? System.Reflection.TargetInvocationException -> None + // | :? System.NullReferenceException -> None - /// Updates property value by given function - let tryUpdatePropertyValueFromName (o:obj) (propName:string) (f: 'a -> 'a) = - let v = optBuildApply f (tryGetPropertyValueAs<'a> o propName) - trySetPropertyValue o propName v - //o + ///// Updates property value by given function + //let tryUpdatePropertyValueFromName (o:obj) (propName:string) (f: 'a -> 'a) = + // let v = optBuildApply f (tryGetPropertyValueAs<'a> o propName) + // trySetPropertyValue o propName v + // //o - /// Updates property value by given function - let tryUpdatePropertyValue (o:obj) (expr : Microsoft.FSharp.Quotations.Expr) (f: 'a -> 'a) = - let propName = tryGetPropertyName expr - let g = (tryGetPropertyValueAs<'a> o propName.Value) - let v = optBuildApply f g - trySetPropertyValue o propName.Value v - //o + ///// Updates property value by given function + //let tryUpdatePropertyValue (o:obj) (expr : Microsoft.FSharp.Quotations.Expr) (f: 'a -> 'a) = + // let propName = tryGetPropertyName expr + // let g = (tryGetPropertyValueAs<'a> o propName.Value) + // let v = optBuildApply f g + // trySetPropertyValue o propName.Value v + // //o - let updatePropertyValueAndIgnore (o:obj) (expr : Microsoft.FSharp.Quotations.Expr) (f: 'a -> 'a) = - tryUpdatePropertyValue o expr f |> ignore + //let updatePropertyValueAndIgnore (o:obj) (expr : Microsoft.FSharp.Quotations.Expr) (f: 'a -> 'a) = + // tryUpdatePropertyValue o expr f |> ignore - /// Removes property - let removeProperty (o:obj) (propName:string) = + /// Sets property value using reflection + #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT + [] + #endif + let removeProperty (o:obj) (propName:string) = + #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT + jsNative + #else match tryGetPropertyInfo o propName with | Some property -> try @@ -102,4 +130,4 @@ module ReflectionUtils = | :? System.ArgumentException -> false | :? System.NullReferenceException -> false | None -> false - + #endif From 159da5d39d210ff491e5b5cbe44ea8762225b5a4 Mon Sep 17 00:00:00 2001 From: HLWeil Date: Tue, 30 Jul 2024 17:03:51 +0400 Subject: [PATCH 03/20] split test files and start reworking towards pyxpecto --- DynamicObj.sln | 32 ++- build/ProjectInfo.fs | 5 +- tests/CSharpTests/CSharpTests.csproj | 1 + .../DynamicObject.Immutable.Tests.fsproj} | 2 +- .../ImmTests.fs | 0 .../OperatorsTests.fs | 0 .../Program.fs | 0 tests/DynamicObject.Tests/DynamicObj.fs | 213 ++++++++++++++++++ .../DynamicObject.Tests.fsproj | 34 +++ tests/DynamicObject.Tests/Main.fs | 11 + tests/DynamicObject.Tests/ReflectionUtils.fs | 15 ++ tests/UnitTests/Tests.fs | 211 ----------------- 12 files changed, 301 insertions(+), 223 deletions(-) rename tests/{UnitTests/UnitTests.fsproj => DynamicObject.Immutable.Tests/DynamicObject.Immutable.Tests.fsproj} (92%) rename tests/{UnitTests => DynamicObject.Immutable.Tests}/ImmTests.fs (100%) rename tests/{UnitTests => DynamicObject.Immutable.Tests}/OperatorsTests.fs (100%) rename tests/{UnitTests => DynamicObject.Immutable.Tests}/Program.fs (100%) create mode 100644 tests/DynamicObject.Tests/DynamicObj.fs create mode 100644 tests/DynamicObject.Tests/DynamicObject.Tests.fsproj create mode 100644 tests/DynamicObject.Tests/Main.fs create mode 100644 tests/DynamicObject.Tests/ReflectionUtils.fs delete mode 100644 tests/UnitTests/Tests.fs diff --git a/DynamicObj.sln b/DynamicObj.sln index a64b218..bec13c1 100644 --- a/DynamicObj.sln +++ b/DynamicObj.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 17.0.31521.260 MinimumVisualStudioVersion = 10.0.40219.1 Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "DynamicObj", "src\DynamicObj\DynamicObj.fsproj", "{B8BF1554-AAC3-434E-9502-FC83B43F3704}" EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "UnitTests", "tests\UnitTests\UnitTests.fsproj", "{D009964D-9408-4344-B610-B73F54FE2A86}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{39AA72A1-A628-481B-A2B5-94E2BD163061}" ProjectSection(SolutionItems) = preProject build.cmd = build.cmd @@ -26,17 +24,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".proj", ".proj", "{C3CF2F15 global.json = global.json key.snk = key.snk LICENSE = LICENSE + package.json = package.json README.md = README.md RELEASE_NOTES.md = RELEASE_NOTES.md EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{42AA66FC-8928-4029-BF41-52C1B49DEEDF}" ProjectSection(SolutionItems) = preProject + docs\content\fsdocs-custom.css = docs\content\fsdocs-custom.css + docs\index.fsx = docs\index.fsx docs\_template.fsx = docs\_template.fsx docs\_template.html = docs\_template.html docs\_template.ipynb = docs\_template.ipynb - docs\content\fsdocs-custom.css = docs\content\fsdocs-custom.css - docs\index.fsx = docs\index.fsx EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpTests", "tests\CSharpTests\CSharpTests.csproj", "{D62D0901-DB69-4C64-AC63-FBBBDCF6BC7D}" @@ -45,6 +44,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{988D804A EndProject Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "build", "build\build.fsproj", "{C73AB951-91F2-4668-B2E0-B58298E5F664}" EndProject +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "DynamicObj.Immutable", "src\DynamicObj.Immutable\DynamicObj.Immutable.fsproj", "{5E7DAC28-7752-4209-B3BB-6DCE54C28AD8}" +EndProject +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "DynamicObject.Tests", "tests\DynamicObject.Tests\DynamicObject.Tests.fsproj", "{39192F2D-164B-4905-A7D7-5C5B0FFCD2BB}" +EndProject +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "DynamicObject.Immutable.Tests", "tests\DynamicObject.Immutable.Tests\DynamicObject.Immutable.Tests.fsproj", "{0F6A539F-82D2-4BDC-8BF0-F2D261873B92}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -55,10 +60,6 @@ Global {B8BF1554-AAC3-434E-9502-FC83B43F3704}.Debug|Any CPU.Build.0 = Debug|Any CPU {B8BF1554-AAC3-434E-9502-FC83B43F3704}.Release|Any CPU.ActiveCfg = Release|Any CPU {B8BF1554-AAC3-434E-9502-FC83B43F3704}.Release|Any CPU.Build.0 = Release|Any CPU - {D009964D-9408-4344-B610-B73F54FE2A86}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D009964D-9408-4344-B610-B73F54FE2A86}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D009964D-9408-4344-B610-B73F54FE2A86}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D009964D-9408-4344-B610-B73F54FE2A86}.Release|Any CPU.Build.0 = Release|Any CPU {D62D0901-DB69-4C64-AC63-FBBBDCF6BC7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D62D0901-DB69-4C64-AC63-FBBBDCF6BC7D}.Debug|Any CPU.Build.0 = Debug|Any CPU {D62D0901-DB69-4C64-AC63-FBBBDCF6BC7D}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -67,14 +68,27 @@ Global {C73AB951-91F2-4668-B2E0-B58298E5F664}.Debug|Any CPU.Build.0 = Debug|Any CPU {C73AB951-91F2-4668-B2E0-B58298E5F664}.Release|Any CPU.ActiveCfg = Release|Any CPU {C73AB951-91F2-4668-B2E0-B58298E5F664}.Release|Any CPU.Build.0 = Release|Any CPU + {5E7DAC28-7752-4209-B3BB-6DCE54C28AD8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5E7DAC28-7752-4209-B3BB-6DCE54C28AD8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5E7DAC28-7752-4209-B3BB-6DCE54C28AD8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5E7DAC28-7752-4209-B3BB-6DCE54C28AD8}.Release|Any CPU.Build.0 = Release|Any CPU + {39192F2D-164B-4905-A7D7-5C5B0FFCD2BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39192F2D-164B-4905-A7D7-5C5B0FFCD2BB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39192F2D-164B-4905-A7D7-5C5B0FFCD2BB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39192F2D-164B-4905-A7D7-5C5B0FFCD2BB}.Release|Any CPU.Build.0 = Release|Any CPU + {0F6A539F-82D2-4BDC-8BF0-F2D261873B92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F6A539F-82D2-4BDC-8BF0-F2D261873B92}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F6A539F-82D2-4BDC-8BF0-F2D261873B92}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F6A539F-82D2-4BDC-8BF0-F2D261873B92}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {D009964D-9408-4344-B610-B73F54FE2A86} = {988D804A-3A42-4E46-B233-B64F5C22524B} {D62D0901-DB69-4C64-AC63-FBBBDCF6BC7D} = {988D804A-3A42-4E46-B233-B64F5C22524B} {C73AB951-91F2-4668-B2E0-B58298E5F664} = {39AA72A1-A628-481B-A2B5-94E2BD163061} + {39192F2D-164B-4905-A7D7-5C5B0FFCD2BB} = {988D804A-3A42-4E46-B233-B64F5C22524B} + {0F6A539F-82D2-4BDC-8BF0-F2D261873B92} = {988D804A-3A42-4E46-B233-B64F5C22524B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6F5C3597-4524-4A4E-94EC-44857BD0BCEC} diff --git a/build/ProjectInfo.fs b/build/ProjectInfo.fs index 91d9cc9..4c8ee68 100644 --- a/build/ProjectInfo.fs +++ b/build/ProjectInfo.fs @@ -9,8 +9,9 @@ let summary = "F# library supporting Dynamic Objects including inheritance in fu let testProjects = [ - "tests/UnitTests/UnitTests.fsproj" - "tests/CSharpTests/CSharpTests.csproj" + "tests/DynamicObject.Tests" + "tests/DynamicObject.Immutable.Tests" + "tests/CSharpTests" ] let solutionFile = $"{project}.sln" diff --git a/tests/CSharpTests/CSharpTests.csproj b/tests/CSharpTests/CSharpTests.csproj index 430a89f..faf0826 100644 --- a/tests/CSharpTests/CSharpTests.csproj +++ b/tests/CSharpTests/CSharpTests.csproj @@ -19,6 +19,7 @@ + diff --git a/tests/UnitTests/UnitTests.fsproj b/tests/DynamicObject.Immutable.Tests/DynamicObject.Immutable.Tests.fsproj similarity index 92% rename from tests/UnitTests/UnitTests.fsproj rename to tests/DynamicObject.Immutable.Tests/DynamicObject.Immutable.Tests.fsproj index 52aeff6..d14214a 100644 --- a/tests/UnitTests/UnitTests.fsproj +++ b/tests/DynamicObject.Immutable.Tests/DynamicObject.Immutable.Tests.fsproj @@ -9,7 +9,6 @@ - @@ -28,6 +27,7 @@ + diff --git a/tests/UnitTests/ImmTests.fs b/tests/DynamicObject.Immutable.Tests/ImmTests.fs similarity index 100% rename from tests/UnitTests/ImmTests.fs rename to tests/DynamicObject.Immutable.Tests/ImmTests.fs diff --git a/tests/UnitTests/OperatorsTests.fs b/tests/DynamicObject.Immutable.Tests/OperatorsTests.fs similarity index 100% rename from tests/UnitTests/OperatorsTests.fs rename to tests/DynamicObject.Immutable.Tests/OperatorsTests.fs diff --git a/tests/UnitTests/Program.fs b/tests/DynamicObject.Immutable.Tests/Program.fs similarity index 100% rename from tests/UnitTests/Program.fs rename to tests/DynamicObject.Immutable.Tests/Program.fs diff --git a/tests/DynamicObject.Tests/DynamicObj.fs b/tests/DynamicObject.Tests/DynamicObj.fs new file mode 100644 index 0000000..e8dff68 --- /dev/null +++ b/tests/DynamicObject.Tests/DynamicObj.fs @@ -0,0 +1,213 @@ +module DynamicObj.Tests + +open System +open Fable.Pyxpecto +open DynamicObj + + +let tests_set = testList "Set" [ + + testCase "Same String" <| fun _ -> + let a = DynamicObj () + a.SetValue("aaa", 5) + let b = DynamicObj () + b.SetValue("aaa", 5) + Expect.equal a b "Values should be equal" + Expect.equal (a.GetHashCode()) (b.GetHashCode()) "Hash codes should be equal" + + + testCase "Different Strings" <| fun _ -> + let a = DynamicObj () + a.SetValue("aaa", 1212) + let b = DynamicObj () + b.SetValue("aaa", 5) + Expect.notEqual a b "Values should not be equal" + + testCase "Same list" <| fun _ -> + let a' = DynamicObj () + let b' = DynamicObj () + a'.SetValue("quack!", [1; 2; 3]) + b'.SetValue("quack!1", [1; 2; 3]) + Expect.notEqual (a'.GetHashCode()) (b'.GetHashCode()) "Hash codes should not be equal" + + testCase "Different lists" <| fun _ -> + let a' = DynamicObj () + let b' = DynamicObj () + a'.SetValue("quack!", [1; 2; 3]) + b'.SetValue("quack!", [1; 2; 3; 4; 34]) + Expect.notEqual (a'.GetHashCode()) (b'.GetHashCode()) "Hash codes should not be equal" + + testCase "Nested Same List Same String" <| fun _ -> + let a = DynamicObj () + let b = DynamicObj () + + let a' = DynamicObj () + let b' = DynamicObj () + a'.SetValue("quack!", [1; 2; 3]) + b'.SetValue("quack!", [1; 2; 3]) + + a.SetValue("aaa", a') + b.SetValue("aaa", b') + Expect.equal a' b' "New Values should be equal" + Expect.equal a b "Old Values should be equal" + Expect.equal (a.GetHashCode()) (b.GetHashCode()) "Old Hash codes should be equal" + Expect.equal (a'.GetHashCode()) (b'.GetHashCode()) "New Hash codes should be equal" + + testCase "Nested Same List Different Strings" <| fun _ -> + let a = DynamicObj () + let b = DynamicObj () + + let a' = DynamicObj () + let b' = DynamicObj () + a'.SetValue("quack!", [1; 2; 3]) + b'.SetValue("quack!", [1; 2; 3]) + + a.SetValue("aaa", a') + b.SetValue("aaa1", b') + Expect.equal a' b' "New Values should be equal" + Expect.notEqual a b "Old Values should not be equal" + Expect.equal (a'.GetHashCode()) (b'.GetHashCode()) "New Hash codes should be equal" + ] + +let tests_remove = testList "Remove" [ + + testCase "Nested Remove only on one" <| fun _ -> + let a = DynamicObj () + let b = DynamicObj () + + let a' = DynamicObj () + let b' = DynamicObj () + a'.SetValue("quack!", [1; 2; 3]) + b'.SetValue("quack!", [1; 2; 3]) + + a.SetValue("aaa", a') + a.Remove "quack!" |> ignore + b.SetValue("aaa", b') + + Expect.notEqual a b "Values should be unequal" + Expect.notEqual (a.GetHashCode()) (b.GetHashCode()) "Hash codes should be unequal" + + testCase "Nested Remove on both" <| fun _ -> + let a = DynamicObj () + let b = DynamicObj () + + let a' = DynamicObj () + let b' = DynamicObj () + a'.SetValue("quack!", [1; 2; 3]) + b'.SetValue("quack!", [1; 2; 3]) + + a.SetValue("aaa", a') + a.Remove "quack!" |> ignore + b.SetValue("aaa", b') + b.Remove "quack!" |> ignore + + Expect.equal a b "Values should be equal" + Expect.equal (a.GetHashCode()) (b.GetHashCode()) "Hash codes should be equal" + +] + + +let tests_formatString = testList "FormatString" [ + + testCase "Format string 1" <| fun _ -> + let foo = DynamicObj() + foo.SetValue("bar", [1;2;3;4]) + let expected = "?bar: [1; 2; 3; ... ]" + Expect.equal expected (foo |> DynObj.format) "Format string 1 failed" + + testCase "Format string 2" <| fun _ -> + let foo = DynamicObj() + foo.SetValue("corgi", "corgi") + let inner = DynamicObj() + inner.SetValue("bar", "baz") + foo.SetValue("foo", inner) + let expected = $"""?corgi: corgi{Environment.NewLine}?foo:{Environment.NewLine} ?bar: baz""" + Expect.equal expected (foo |> DynObj.format) "Format string 2 failed" + +] + + +let tests_combine = testList "Combine" [ + + testCase "Combine flat DOs" <| fun _ -> + let target = DynamicObj() + target.SetValue("target-unique", [42]) + target.SetValue("will-be-overridden", "WAS_NOT_OVERRIDDEN!") + + let source = DynamicObj() + source.SetValue("source-unique", [42; 32]) + source.SetValue("will-be-overridden", "WAS_OVERRIDDEN =)") + + let combined = DynObj.combine target source + + let expected = DynamicObj() + expected.SetValue("target-unique", [42]) + expected.SetValue("source-unique", [42; 32]) + expected.SetValue("will-be-overridden", "WAS_OVERRIDDEN =)") + + Expect.equal expected combined "Combine flat DOs failed" + + testCase "Combine nested DOs" <| fun _ -> + let target = DynamicObj() + + target.SetValue("target-unique", 1337) + target.SetValue("will-be-overridden", -42) + let something2BeCombined = DynamicObj() + something2BeCombined.SetValue("inner","I Am") + let something2BeOverriden = DynamicObj() + something2BeOverriden.SetValue("inner","NOT_OVERRIDDEN") + target.SetValue("nested-will-be-combined", something2BeCombined) + target.SetValue("nested-will-be-overridden", something2BeOverriden) + + let source = DynamicObj() + + source.SetValue("source-unique", 69) + source.SetValue("will-be-overridden", "WAS_OVERRIDDEN") + let alsoSomething2BeCombined = DynamicObj() + alsoSomething2BeCombined.SetValue("inner_combined","Complete") + source.SetValue("nested-will-be-combined", alsoSomething2BeCombined) + source.SetValue("nested-will-be-overridden", "WAS_OVERRIDDEN") + + let combined = DynObj.combine target source + + let expected = DynamicObj() + + expected.SetValue("source-unique", 69) + expected.SetValue("target-unique", 1337) + expected.SetValue("will-be-overridden", "WAS_OVERRIDDEN") + expected.SetValue("nested-will-be-overridden", "WAS_OVERRIDDEN") + expected.SetValue("nested-will-be-combined", + let inner = DynamicObj() + inner.SetValue("inner","I Am") + inner.SetValue("inner_combined","Complete") + inner + ) + + Expect.equal expected combined "Combine nested DOs failed" +] + +let tests_print = testList "Print" [ + + testCase "Test Print For Issue 14" <| fun _ -> + let outer = DynamicObj() + let inner = DynamicObj() + inner.SetValue("Level", "Information") + inner.SetValue("MessageTemplate","{Method} Request at {Path}") + outer.SetValue("serilog", inner) + + let print = + try + outer |> DynObj.print + true + with + | e -> false + + Expect.isTrue print "Print failed for issue 14" +] + +let main = testList "DynamicObj" [ + tests_set + tests_remove + tests_formatString + tests_combine +] \ No newline at end of file diff --git a/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj b/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj new file mode 100644 index 0000000..995f36d --- /dev/null +++ b/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj @@ -0,0 +1,34 @@ + + + + net6.0 + + false + false + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/tests/DynamicObject.Tests/Main.fs b/tests/DynamicObject.Tests/Main.fs new file mode 100644 index 0000000..7094b03 --- /dev/null +++ b/tests/DynamicObject.Tests/Main.fs @@ -0,0 +1,11 @@ +module Main.Tests + +open Fable.Pyxpecto + +let all = testSequenced <| testList "DynamicObj" [ + DynamicObj.Tests.main + ReflectionUtils.Tests.main +] + +[] +let main argv = Pyxpecto.runTests [||] all \ No newline at end of file diff --git a/tests/DynamicObject.Tests/ReflectionUtils.fs b/tests/DynamicObject.Tests/ReflectionUtils.fs new file mode 100644 index 0000000..b8fdb41 --- /dev/null +++ b/tests/DynamicObject.Tests/ReflectionUtils.fs @@ -0,0 +1,15 @@ +module ReflectionUtils.Tests + +open System +open Fable.Pyxpecto +open DynamicObj + + +let tests_baseObject = testList "Dynamic Set" [ + testCase "Same String" <| fun _ -> + Expect.isTrue false "ehllo" +] + +let main = testList "DynamicObj" [ + tests_baseObject +] \ No newline at end of file diff --git a/tests/UnitTests/Tests.fs b/tests/UnitTests/Tests.fs deleted file mode 100644 index cdcb6e7..0000000 --- a/tests/UnitTests/Tests.fs +++ /dev/null @@ -1,211 +0,0 @@ -module Tests.EqualityHashcode - -open System -open Xunit -open DynamicObj - -[] -let ``Equality test 1`` () = - let a = DynamicObj () - a.SetValue("aaa", 5) - let b = DynamicObj () - b.SetValue("aaa", 5) - Assert.Equal(a, b) - Assert.Equal(a.GetHashCode(), b.GetHashCode()) - -[] -let ``Equality test 2`` () = - let a = DynamicObj () - a.SetValue("aaa", 1212) - let b = DynamicObj () - b.SetValue("aaa", 5) - Assert.NotEqual(a, b) - - -[] -let ``Equality test 3`` () = - let a = DynamicObj () - let b = DynamicObj () - - let a' = DynamicObj () - let b' = DynamicObj () - a'.SetValue("quack!", [1; 2; 3]) - b'.SetValue("quack!", [1; 2; 3]) - - a.SetValue("aaa", a') - b.SetValue("aaa", b') - Assert.Equal(a', b') - Assert.Equal(a, b) - Assert.Equal(a.GetHashCode(), b.GetHashCode()) - Assert.Equal(a'.GetHashCode(), b'.GetHashCode()) - -[] -let ``Equality test 4`` () = - let a = DynamicObj () - let b = DynamicObj () - - let a' = DynamicObj () - let b' = DynamicObj () - a'.SetValue("quack!", [1; 2; 3]) - b'.SetValue("quack!", [1; 2; 3]) - - a.SetValue("aaa", a') - b.SetValue("aaa1", b') - Assert.Equal(a', b') - Assert.NotEqual(a, b) - Assert.Equal(a'.GetHashCode(), b'.GetHashCode()) - - -[] -let ``Equality test 5`` () = - let a = DynamicObj () - a.SetValue("bvbb", 5) - let b = DynamicObj () - b.SetValue("aaa", 5) - - a.SetValue("aaa", 5) - a.Remove "bvbb" |> ignore - - Assert.Equal(a, b) - Assert.Equal(a.GetHashCode(), b.GetHashCode()) - -[] -let ``Equality test 6`` () = - //nesting - let a = DynamicObj () - let b = DynamicObj () - b.SetValue("a", 5) - a.SetValue("inner",b) - let c = DynamicObj () - let d = DynamicObj () - d.SetValue("a", 5) - c.SetValue("inner",d) - - Assert.Equal(a, c) - Assert.Equal(a.GetHashCode(), c.GetHashCode()) - -// different objects do NOT have to have different hash -// codes, so here we rely on our luckiness. - -[] -let ``Hashcode inequality 1`` () = - let a' = DynamicObj () - let b' = DynamicObj () - a'.SetValue("quack!", [1; 2; 3]) - b'.SetValue("quack!", [1; 2; 3; 4; 34]) - Assert.NotEqual(a'.GetHashCode(), b'.GetHashCode()) - - -[] -let ``Hashcode inequality 2`` () = - let a' = DynamicObj () - let b' = DynamicObj () - a'.SetValue("quack!", [1; 2; 3]) - b'.SetValue("quack!1", [1; 2; 3]) - Assert.NotEqual(a'.GetHashCode(), b'.GetHashCode()) - -[] -let ``Format string 1`` () = - - let foo = DynamicObj() - foo?bar <- [1;2;3;4] - - let expected = "?bar: [1; 2; 3; ... ]" - - Assert.Equal(expected, (foo |> DynObj.format)) - -[] -let ``Format string 2`` () = - - // nested - let foo = DynamicObj() - foo?corgi <- "corgi" - let inner = DynamicObj() - inner?bar <- "baz" - foo?foo <- inner - - let expected = $"""?corgi: corgi{Environment.NewLine}?foo:{Environment.NewLine} ?bar: baz""" - - Assert.Equal(expected, (foo |> DynObj.format)) - - - -[] -let ``combine flat DOs``() = - let target = DynamicObj() - - target.SetValue("target-unique", [42]) - target.SetValue("will-be-overridden", "WAS_NOT_OVERRIDDEN!") - - let source = DynamicObj() - - source.SetValue("source-unique", [42; 32]) - source.SetValue("will-be-overridden", "WAS_OVERRIDDEN =)") - - let combined = DynObj.combine target source - - let expected = DynamicObj() - - expected.SetValue("target-unique", [42]) - expected.SetValue("source-unique", [42; 32]) - expected.SetValue("will-be-overridden", "WAS_OVERRIDDEN =)") - - Assert.Equal(expected, combined) - -[] -let ``combine nested DOs``() = - - let target = DynamicObj() - - target.SetValue("target-unique", 1337) - target.SetValue("will-be-overridden", -42) - let something2BeCombined = DynamicObj() - something2BeCombined.SetValue("inner","I Am") - let something2BeOverriden = DynamicObj() - something2BeOverriden.SetValue("inner","NOT_OVERRIDDEN") - target.SetValue("nested-will-be-combined", something2BeCombined) - target.SetValue("nested-will-be-overridden", something2BeOverriden) - - let source = DynamicObj() - - source.SetValue("source-unique", 69) - source.SetValue("will-be-overridden", "WAS_OVERRIDDEN") - let alsoSomething2BeCombined = DynamicObj() - alsoSomething2BeCombined.SetValue("inner_combined","Complete") - source.SetValue("nested-will-be-combined", alsoSomething2BeCombined) - source.SetValue("nested-will-be-overridden", "WAS_OVERRIDDEN") - - let combined = DynObj.combine target source - - let expected = DynamicObj() - - expected.SetValue("source-unique", 69) - expected.SetValue("target-unique", 1337) - expected.SetValue("will-be-overridden", "WAS_OVERRIDDEN") - expected.SetValue("nested-will-be-overridden", "WAS_OVERRIDDEN") - expected.SetValue("nested-will-be-combined", - let inner = DynamicObj() - inner.SetValue("inner","I Am") - inner.SetValue("inner_combined","Complete") - inner - ) - - Assert.Equal(expected, combined) - -[] -let ``test print for issue 14``() = - // https://github.com/CSBiology/DynamicObj/issues/14 - let outer = DynamicObj() - let inner = DynamicObj() - inner.SetValue("Level", "Information") - inner.SetValue("MessageTemplate","{Method} Request at {Path}") - outer.SetValue("serilog", inner) - - let print = - try - outer |> DynObj.print - true - with - | e -> false - - Assert.True(print) \ No newline at end of file From 33f5631807e5f63a6ee3eba660da12eb7afbb063 Mon Sep 17 00:00:00 2001 From: HLWeil Date: Tue, 30 Jul 2024 17:04:11 +0400 Subject: [PATCH 04/20] small fixes to library helper files --- .config/dotnet-tools.json | 6 ++++++ .gitignore | 4 +++- build/TestTasks.fs | 14 +++----------- package.json | 13 +++++++++++++ 4 files changed, 25 insertions(+), 12 deletions(-) create mode 100644 package.json diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index aedc80c..f2c8940 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -7,6 +7,12 @@ "commands": [ "fsdocs" ] + }, + "fable": { + "version": "4.19.3", + "commands": [ + "fable" + ] } } } \ No newline at end of file diff --git a/.gitignore b/.gitignore index 05696af..4155693 100644 --- a/.gitignore +++ b/.gitignore @@ -194,4 +194,6 @@ docsrc/tools/FSharp.Formatting.svclog /tests/FSharp.Stats.Tests/coverage.xml .ionide -pkg \ No newline at end of file +pkg +/tests/**/js +/tests/**/py \ No newline at end of file diff --git a/build/TestTasks.fs b/build/TestTasks.fs index 1cdf128..8d3f615 100644 --- a/build/TestTasks.fs +++ b/build/TestTasks.fs @@ -46,20 +46,12 @@ module RunTests = } let runTestsDotnet = BuildTask.create "runTestsDotnet" [clean; build] { + let dotnetRun = run dotnet "run" testProjects - |> Seq.iter (fun testProject -> - Fake.DotNet.DotNet.test(fun testParams -> - { - testParams with - Logger = Some "console;verbosity=detailed" - Configuration = DotNet.BuildConfiguration.fromString configuration - NoBuild = true - } - ) testProject - ) + |> Seq.iter dotnetRun } -let runTests = BuildTask.create "RunTests" [clean; build; RunTests.runTestsJs; (*RunTests.runTestsJsNative; *)RunTests.runTestsPy; (*RunTests.runTestsPyNative; *)RunTests.runTestsDotnet] { +let runTests = BuildTask.create "RunTests" [clean; build; RunTests.runTestsJs; (*RunTests.runTestsJsNative; *)(*RunTests.runTestsPy;*) (*RunTests.runTestsPyNative; *)RunTests.runTestsDotnet] { () } diff --git a/package.json b/package.json new file mode 100644 index 0000000..7a950e4 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "type": "module", + "scripts": { + "mocha": "mocha" + }, + "dependencies": { + "fable-library": "^1.1.1" + }, + "devDependencies": { + "mkdirp": "3.0.1", + "mocha": "^10.2.0" + } +} From a231000e131ee770649e30a00fbc6dce61780292 Mon Sep 17 00:00:00 2001 From: HLWeil Date: Fri, 2 Aug 2024 14:38:34 +0400 Subject: [PATCH 05/20] hotfix rework of DynamicObj equality #22 --- src/DynamicObj/DynamicObj.fs | 10 ++-- src/DynamicObj/Playground.fsx | 66 ++++--------------------- tests/DynamicObject.Tests/DynamicObj.fs | 39 +++++++++++++-- 3 files changed, 46 insertions(+), 69 deletions(-) diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index 8d585ac..a6ef716 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -79,6 +79,7 @@ type DynamicObj internal (dict:Dictionary) = new KeyValuePair(key, properties.[key]); ] #endif + |> Seq.filter (fun kv -> kv.Key.ToLower() <> "properties") member this.GetDynamicMemberNames() = properties.Keys @@ -116,16 +117,11 @@ type DynamicObj internal (dict:Dictionary) = override this.Equals o = match o with | :? DynamicObj as other -> - let subdictOf (super : Dictionary<'a, 'b>) (dict : Dictionary<'a, 'b>) = - dict - |> Seq.forall (fun pair -> - let (contains, value) = super.TryGetValue pair.Key - contains && value.Equals(pair.Value)) - subdictOf this.Properties other.Properties + this.GetHashCode() = other.GetHashCode() | _ -> false override this.GetHashCode () = - this.Properties + this.GetProperties(true) |> List.ofSeq |> List.sortBy (fun pair -> pair.Key) |> List.map (fun pair -> struct (pair.Key, pair.Value)) diff --git a/src/DynamicObj/Playground.fsx b/src/DynamicObj/Playground.fsx index 0682437..620bbf8 100644 --- a/src/DynamicObj/Playground.fsx +++ b/src/DynamicObj/Playground.fsx @@ -1,67 +1,19 @@ #r "nuget: Newtonsoft.Json" +#r "nuget: Fable.Core" +#r "nuget: Fable.Pyxpecto" + #load "./ReflectionUtils.fs" -#load "./ImmutableDynamicObj.fs" #load "./DynamicObj.fs" #load "./DynObj.fs" -#load "./Operators.fs" +open Fable.Pyxpecto open DynamicObj -open DynamicObj.Operators - -let target = DynamicObj() - -target.SetValue("target-unique", [42]) -target.SetValue("will-be-overridden", "WAS_NOT_OVERRIDDEN!") - -let source = DynamicObj() - -source.SetValue("source-unique", [|42|]) -source.SetValue("will-be-overridden", "WAS_OVERRIDDEN =)") - -let combined = DynObj.combine target source - -let expected = DynamicObj() - -expected.SetValue("target-unique", [42]) -expected.SetValue("will-be-overridden", "WAS_OVERRIDDEN =)") -expected.SetValue("source-unique", [|42|]) - -combined = expected - -combined |> DynObj.print -expected |> DynObj.print - - -let foo = DynamicObj() -foo?bar <- [1;2;3;4] - -(DynObj.print foo) - -let fooIDO = - ImmutableDynamicObj() - |> ImmutableDynamicObj.add "foo" "bar" - |> ImmutableDynamicObj.add "inner" (ImmutableDynamicObj() |> ImmutableDynamicObj.add "innerfoo" "innerbar" ) - |> ImmutableDynamicObj.add "inner2" (ImmutableDynamicObj() |> ImmutableDynamicObj.add "innerinner" (ImmutableDynamicObj() |> ImmutableDynamicObj.add "innerinnerfoo" "innerinnerbar" )) - -printfn "%s" (fooIDO |> ImmutableDynamicObj.format) -let o2 = - ImmutableDynamicObj.empty - ++ ("aaa", 5) - ++ ("ohno", 10) - ++ ("quack", "tt") - ++ ("hh", [1; 2; 3]) - -let o = - ImmutableDynamicObj.empty - ++ ("aaa", 5) - ++ ("ohno", 10) - ++ ("quack", "tt") - ++ ("hh", [1; 2; 3]) - ++ ("inner", o2) -open Newtonsoft.Json +let a = DynamicObj () +a.SetValue("aaa", 5) +let b = DynamicObj () +b.SetValue("aaa", 5) -let actual = JsonConvert.SerializeObject o -printfn "%s" actual \ No newline at end of file +a.GetProperties(true) \ No newline at end of file diff --git a/tests/DynamicObject.Tests/DynamicObj.fs b/tests/DynamicObject.Tests/DynamicObj.fs index e8dff68..cb3a3b4 100644 --- a/tests/DynamicObject.Tests/DynamicObj.fs +++ b/tests/DynamicObject.Tests/DynamicObj.fs @@ -15,7 +15,6 @@ let tests_set = testList "Set" [ Expect.equal a b "Values should be equal" Expect.equal (a.GetHashCode()) (b.GetHashCode()) "Hash codes should be equal" - testCase "Different Strings" <| fun _ -> let a = DynamicObj () a.SetValue("aaa", 1212) @@ -23,6 +22,14 @@ let tests_set = testList "Set" [ b.SetValue("aaa", 5) Expect.notEqual a b "Values should not be equal" + testCase "String only on one" <| fun _ -> + let a = DynamicObj () + let b = DynamicObj () + b.SetValue("aaa", 5) + + Expect.notEqual a b "Values should not be equal" + Expect.notEqual b a "Values should not be equal (Reversed equality)" + testCase "Same list" <| fun _ -> let a' = DynamicObj () let b' = DynamicObj () @@ -71,7 +78,7 @@ let tests_set = testList "Set" [ let tests_remove = testList "Remove" [ - testCase "Nested Remove only on one" <| fun _ -> + testCase "Nested Remove Non-Existing" <| fun _ -> let a = DynamicObj () let b = DynamicObj () @@ -84,9 +91,28 @@ let tests_remove = testList "Remove" [ a.Remove "quack!" |> ignore b.SetValue("aaa", b') + Expect.equal a b "Values should be unequal" + Expect.equal (a.GetHashCode()) (b.GetHashCode()) "Hash codes should be unequal" + + testCase "Nested Remove only on one" <| fun _ -> + let a = DynamicObj () + let b = DynamicObj () + + let a' = DynamicObj () + let b' = DynamicObj () + a'.SetValue("quack!", [1; 2; 3]) + b'.SetValue("quack!", [1; 2; 3]) + + a.SetValue("aaa", a') + a'.Remove "quack!" |> ignore + b.SetValue("aaa", b') + Expect.notEqual a b "Values should be unequal" Expect.notEqual (a.GetHashCode()) (b.GetHashCode()) "Hash codes should be unequal" + testCase "Pypecto unequality" <| fun _ -> + Expect.notEqual 1 2 "hello" + testCase "Nested Remove on both" <| fun _ -> let a = DynamicObj () let b = DynamicObj () @@ -131,16 +157,19 @@ let tests_combine = testList "Combine" [ testCase "Combine flat DOs" <| fun _ -> let target = DynamicObj() + target.SetValue("target-unique", [42]) target.SetValue("will-be-overridden", "WAS_NOT_OVERRIDDEN!") let source = DynamicObj() + source.SetValue("source-unique", [42; 32]) source.SetValue("will-be-overridden", "WAS_OVERRIDDEN =)") let combined = DynObj.combine target source let expected = DynamicObj() + expected.SetValue("target-unique", [42]) expected.SetValue("source-unique", [42; 32]) expected.SetValue("will-be-overridden", "WAS_OVERRIDDEN =)") @@ -158,7 +187,7 @@ let tests_combine = testList "Combine" [ something2BeOverriden.SetValue("inner","NOT_OVERRIDDEN") target.SetValue("nested-will-be-combined", something2BeCombined) target.SetValue("nested-will-be-overridden", something2BeOverriden) - + let source = DynamicObj() source.SetValue("source-unique", 69) @@ -167,9 +196,9 @@ let tests_combine = testList "Combine" [ alsoSomething2BeCombined.SetValue("inner_combined","Complete") source.SetValue("nested-will-be-combined", alsoSomething2BeCombined) source.SetValue("nested-will-be-overridden", "WAS_OVERRIDDEN") - + let combined = DynObj.combine target source - + let expected = DynamicObj() expected.SetValue("source-unique", 69) From f6eefdbdd9bb21a2a76310739a579c65744eb329 Mon Sep 17 00:00:00 2001 From: HLWeil Date: Mon, 5 Aug 2024 17:16:18 +0200 Subject: [PATCH 06/20] fix remove function for javascript --- src/DynamicObj/DynObj.fs | 8 ++-- src/DynamicObj/DynamicObj.fs | 15 +++++--- src/DynamicObj/ReflectionUtils.fs | 2 +- tests/DynamicObject.Tests/DynamicObj.fs | 39 +++++++++++++++++++- tests/DynamicObject.Tests/ReflectionUtils.fs | 5 ++- 5 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/DynamicObj/DynObj.fs b/src/DynamicObj/DynObj.fs index 04a8053..f57adbd 100644 --- a/src/DynamicObj/DynObj.fs +++ b/src/DynamicObj/DynObj.fs @@ -5,26 +5,26 @@ open System.Collections.Generic module DynObj = /// New DynamicObj of Dictionary - let ofDict dict = DynamicObj(dict) + let ofDict dict = DynamicObj.fromDict dict /// New DynamicObj of a sequence of key value let ofSeq kv = let dict = new Dictionary() kv |> Seq.iter (fun (k,v) -> dict.Add(k,v)) - DynamicObj(dict) + DynamicObj.fromDict dict /// New DynamicObj of a list of key value let ofList kv = let dict = new Dictionary() kv |> List.iter (fun (k,v) -> dict.Add(k,v)) - DynamicObj(dict) + DynamicObj.fromDict dict /// New DynamicObj of an array of key value let ofArray kv = let dict = new Dictionary() kv |> Array.iter (fun (k,v) -> dict.Add(k,v)) - DynamicObj(dict) + DynamicObj.fromDict dict // diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index a6ef716..542ed41 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -14,16 +14,21 @@ module Fable = let getPropertyNames (o:obj) : string seq = jsNative -type DynamicObj internal (dict:Dictionary) = +[] +type DynamicObj() = - let properties = dict//new Dictionary() + let mutable properties = new Dictionary() - member this.Properties = properties + member this.Properties + with get() = properties + and internal set(value) = properties <- value - /// - new () = DynamicObj(new Dictionary()) + static member fromDict dict = + let obj = DynamicObj() + obj.Properties <- dict + obj /// Gets property value member this.TryGetValue name = diff --git a/src/DynamicObj/ReflectionUtils.fs b/src/DynamicObj/ReflectionUtils.fs index d34d211..4709c5c 100644 --- a/src/DynamicObj/ReflectionUtils.fs +++ b/src/DynamicObj/ReflectionUtils.fs @@ -115,7 +115,7 @@ module ReflectionUtils = /// Sets property value using reflection #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT - [] + [] #endif let removeProperty (o:obj) (propName:string) = #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT diff --git a/tests/DynamicObject.Tests/DynamicObj.fs b/tests/DynamicObject.Tests/DynamicObj.fs index cb3a3b4..78e670c 100644 --- a/tests/DynamicObject.Tests/DynamicObj.fs +++ b/tests/DynamicObject.Tests/DynamicObj.fs @@ -78,6 +78,41 @@ let tests_set = testList "Set" [ let tests_remove = testList "Remove" [ + testCase "Remove" <| fun _ -> + let a = DynamicObj () + let b = DynamicObj () + + a.SetValue("quack!", "hello") + + a.Remove "quack!" |> ignore + + Expect.equal a b "Values should be equal" + Expect.equal (a.GetHashCode()) (b.GetHashCode()) "Hash codes should be equal" + + testCase "Remove Non-Existing" <| fun _ -> + let a = DynamicObj () + let b = DynamicObj () + + a.SetValue("quack!", "hello") + b.SetValue("quack!", "hello") + + a.Remove "quecky!" |> ignore + + Expect.equal a b "Values should be equal" + Expect.equal (a.GetHashCode()) (b.GetHashCode()) "Hash codes should be equal" + + testCase "Remove only on one" <| fun _ -> + let a = DynamicObj () + let b = DynamicObj () + + a.SetValue("quack!", "hello") + b.SetValue("quack!", "hello") + + a.Remove "quack!" |> ignore + + Expect.notEqual a b "Values should be unequal" + Expect.notEqual (a.GetHashCode()) (b.GetHashCode()) "Hash codes should be unequal" + testCase "Nested Remove Non-Existing" <| fun _ -> let a = DynamicObj () let b = DynamicObj () @@ -91,8 +126,8 @@ let tests_remove = testList "Remove" [ a.Remove "quack!" |> ignore b.SetValue("aaa", b') - Expect.equal a b "Values should be unequal" - Expect.equal (a.GetHashCode()) (b.GetHashCode()) "Hash codes should be unequal" + Expect.equal a b "Values should be equal" + Expect.equal (a.GetHashCode()) (b.GetHashCode()) "Hash codes should be equal" testCase "Nested Remove only on one" <| fun _ -> let a = DynamicObj () diff --git a/tests/DynamicObject.Tests/ReflectionUtils.fs b/tests/DynamicObject.Tests/ReflectionUtils.fs index b8fdb41..c4f7f02 100644 --- a/tests/DynamicObject.Tests/ReflectionUtils.fs +++ b/tests/DynamicObject.Tests/ReflectionUtils.fs @@ -7,9 +7,10 @@ open DynamicObj let tests_baseObject = testList "Dynamic Set" [ testCase "Same String" <| fun _ -> - Expect.isTrue false "ehllo" + //ReflectionUtils.tryGetPropertyValue + Expect.isTrue true "daw" ] -let main = testList "DynamicObj" [ +let main = testList "ReflectionUtils" [ tests_baseObject ] \ No newline at end of file From f4e8830b023fb41909e7fb73f0a1c38272efe577 Mon Sep 17 00:00:00 2001 From: HLWeil Date: Mon, 5 Aug 2024 17:37:09 +0200 Subject: [PATCH 07/20] start reworking DynamicObj format function for fable --- src/DynamicObj/DynObj.fs | 11 ++++++----- src/DynamicObj/DynamicObj.fs | 6 +++--- tests/DynamicObject.Tests/DynamicObj.fs | 4 ++-- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/DynamicObj/DynObj.fs b/src/DynamicObj/DynObj.fs index f57adbd..41ae56a 100644 --- a/src/DynamicObj/DynObj.fs +++ b/src/DynamicObj/DynObj.fs @@ -86,21 +86,22 @@ module DynObj = let format (d:DynamicObj) = - let members = d.GetDynamicMemberNames() |> List.ofSeq + let members = d.GetPropertyNames(true) |> List.ofSeq let rec loop (object:DynamicObj) (identationLevel:int) (membersLeft:string list) (acc:string list) = let ident = [for i in 0 .. identationLevel-1 do yield " "] |> String.concat "" match membersLeft with | [] -> acc |> List.rev |> String.concat System.Environment.NewLine | m::rest -> - let item = object?(``m``) + let item = object.TryGetValue m match item with - | :? DynamicObj as item -> - let innerMembers = item.GetDynamicMemberNames() |> Seq.cast |> List.ofSeq + | Some (:? DynamicObj as item) -> + let innerMembers = item.GetPropertyNames(true) |> Seq.cast |> List.ofSeq let innerPrint = (loop item (identationLevel + 1) innerMembers []) loop object identationLevel rest ($"{ident}?{m}:{System.Environment.NewLine}{innerPrint}" :: acc) - | _ -> + | Some item -> loop object identationLevel rest ($"{ident}?{m}: {item}"::acc) + | None -> loop object identationLevel rest acc loop d 0 members [] diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index 542ed41..7cd8598 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -86,9 +86,9 @@ type DynamicObj() = #endif |> Seq.filter (fun kv -> kv.Key.ToLower() <> "properties") - member this.GetDynamicMemberNames() = - properties.Keys - |> seq + member this.GetPropertyNames(includeInstanceProperties) = + this.GetProperties(includeInstanceProperties) + |> Seq.map (fun kv -> kv.Key) /// Operator to access a dynamic member by name static member (?) (lookup:#DynamicObj,name:string) = diff --git a/tests/DynamicObject.Tests/DynamicObj.fs b/tests/DynamicObject.Tests/DynamicObj.fs index 78e670c..fed4650 100644 --- a/tests/DynamicObject.Tests/DynamicObj.fs +++ b/tests/DynamicObject.Tests/DynamicObj.fs @@ -174,7 +174,7 @@ let tests_formatString = testList "FormatString" [ let foo = DynamicObj() foo.SetValue("bar", [1;2;3;4]) let expected = "?bar: [1; 2; 3; ... ]" - Expect.equal expected (foo |> DynObj.format) "Format string 1 failed" + Expect.equal (foo |> DynObj.format) expected "Format string 1 failed" testCase "Format string 2" <| fun _ -> let foo = DynamicObj() @@ -183,7 +183,7 @@ let tests_formatString = testList "FormatString" [ inner.SetValue("bar", "baz") foo.SetValue("foo", inner) let expected = $"""?corgi: corgi{Environment.NewLine}?foo:{Environment.NewLine} ?bar: baz""" - Expect.equal expected (foo |> DynObj.format) "Format string 2 failed" + Expect.equal (foo |> DynObj.format) expected "Format string 2 failed" ] From 0786ee5cc038c1775ce74c1e9e36880df6ef90ae Mon Sep 17 00:00:00 2001 From: HLWeil Date: Tue, 6 Aug 2024 16:30:19 +0200 Subject: [PATCH 08/20] add inheritance tests --- tests/DynamicObject.Tests/DynamicObj.fs | 3 - .../DynamicObject.Tests.fsproj | 1 + tests/DynamicObject.Tests/Inheritance.fs | 156 ++++++++++++++++++ tests/DynamicObject.Tests/Main.fs | 3 +- 4 files changed, 159 insertions(+), 4 deletions(-) create mode 100644 tests/DynamicObject.Tests/Inheritance.fs diff --git a/tests/DynamicObject.Tests/DynamicObj.fs b/tests/DynamicObject.Tests/DynamicObj.fs index fed4650..3ca36ab 100644 --- a/tests/DynamicObject.Tests/DynamicObj.fs +++ b/tests/DynamicObject.Tests/DynamicObj.fs @@ -145,9 +145,6 @@ let tests_remove = testList "Remove" [ Expect.notEqual a b "Values should be unequal" Expect.notEqual (a.GetHashCode()) (b.GetHashCode()) "Hash codes should be unequal" - testCase "Pypecto unequality" <| fun _ -> - Expect.notEqual 1 2 "hello" - testCase "Nested Remove on both" <| fun _ -> let a = DynamicObj () let b = DynamicObj () diff --git a/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj b/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj index 995f36d..33d1bbd 100644 --- a/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj +++ b/tests/DynamicObject.Tests/DynamicObject.Tests.fsproj @@ -8,6 +8,7 @@ + diff --git a/tests/DynamicObject.Tests/Inheritance.fs b/tests/DynamicObject.Tests/Inheritance.fs new file mode 100644 index 0000000..10aafc5 --- /dev/null +++ b/tests/DynamicObject.Tests/Inheritance.fs @@ -0,0 +1,156 @@ +module Inheritance.Tests + +open System +open Fable.Pyxpecto +open DynamicObj +open Fable.Core + +[] +type Person(name : string) = + + inherit DynamicObj() + + let mutable name = name + + member this.Name + with get() = name + and set(value) = name <- value + +let tests_set = testList "Set" [ + + testCase "Static Property" <| fun _ -> + let p = Person("John") + p.SetValue("Name", "Jane") + Expect.equal p.Name "Jane" "Static property should be set" + Expect.equal (p.TryGetValue("Name")) (Some "Jane") "Static property should be retreivable dynamically" + + testCase "Dynamic Property" <| fun _ -> + let p = Person("John") + p.SetValue("Age", 42) + Expect.equal (p.TryGetValue("Age")) (Some 42) "Dynamic property should be set" + Expect.equal (p.TryGetValue("Name")) (Some "John") "Static property should be retreivable dynamically" + + testCase "Dynamic Property Equality" <| fun _ -> + let p1 = Person("John") + let p2 = Person("John") + + p1.SetValue("Age", 42) + p2.SetValue("Age", 42) + + Expect.equal p1 p2 "Values should be equal" + Expect.equal (p1.GetHashCode()) (p2.GetHashCode()) "Hash codes should be equal" + + ptestCase "Dynamic Property Only on one" <| fun _ -> + let p1 = Person("John") + let p2 = Person("John") + + p1.SetValue("Age", 42) + + Expect.notEqual p1 p2 "Values should not be equal" + Expect.notEqual p2 p1 "Values should not be equal (Reversed equality)" + ] + +let tests_remove = testList "Remove" [ + + ptestCase "Returns false on static" <| fun _ -> + let p = Person("John") + + let r = p.Remove("Name") + + Expect.isFalse r "Static property should not be removed" + + testCase "Remove Static" <| fun _ -> + let p = Person("John") + + p.Remove("Name") |> ignore + + Expect.equal p.Name null "Static property should not be removed" + + + testCase "Remove Dynamic" <| fun _ -> + let p = Person("John") + + p.SetValue("Age", 42) + + p.Remove "Age" |> ignore + + let r = p.TryGetValue("Age") + + Expect.isNone r "Dynamic property should be removed" + + testCase "Remove only on one" <| fun _ -> + let p1 = Person("John") + let p2 = Person("John") + + p1.SetValue("Age", 42) + p2.SetValue("Age", 42) + + p1.Remove "Age" |> ignore + + Expect.notEqual p1 p2 "Values should be unequal" + Expect.notEqual (p1.GetHashCode()) (p2.GetHashCode()) "Hash codes should be unequal" + +] + + +let tests_formatString = ptestList "FormatString" [ + + testCase "Format string 1" <| fun _ -> + let foo = DynamicObj() + foo.SetValue("bar", [1;2;3;4]) + let expected = "?bar: [1; 2; 3; ... ]" + Expect.equal (foo |> DynObj.format) expected "Format string 1 failed" + +] + + +let tests_combine = ptestList "Combine" [ + + testCase "Combine flat DOs" <| fun _ -> + let target = DynamicObj() + + target.SetValue("target-unique", [42]) + target.SetValue("will-be-overridden", "WAS_NOT_OVERRIDDEN!") + + let source = DynamicObj() + + source.SetValue("source-unique", [42; 32]) + source.SetValue("will-be-overridden", "WAS_OVERRIDDEN =)") + + let combined = DynObj.combine target source + + let expected = DynamicObj() + + expected.SetValue("target-unique", [42]) + expected.SetValue("source-unique", [42; 32]) + expected.SetValue("will-be-overridden", "WAS_OVERRIDDEN =)") + + Expect.equal expected combined "Combine flat DOs failed" + +] + +let tests_print = ptestList "Print" [ + + testCase "Test Print For Issue 14" <| fun _ -> + let outer = DynamicObj() + let inner = DynamicObj() + inner.SetValue("Level", "Information") + inner.SetValue("MessageTemplate","{Method} Request at {Path}") + outer.SetValue("serilog", inner) + + let print = + try + outer |> DynObj.print + true + with + | e -> false + + Expect.isTrue print "Print failed for issue 14" +] + +let main = testList "Inheritance" [ + tests_set + tests_remove + tests_formatString + tests_combine +] \ No newline at end of file diff --git a/tests/DynamicObject.Tests/Main.fs b/tests/DynamicObject.Tests/Main.fs index 7094b03..50759b3 100644 --- a/tests/DynamicObject.Tests/Main.fs +++ b/tests/DynamicObject.Tests/Main.fs @@ -3,8 +3,9 @@ module Main.Tests open Fable.Pyxpecto let all = testSequenced <| testList "DynamicObj" [ - DynamicObj.Tests.main ReflectionUtils.Tests.main + DynamicObj.Tests.main + Inheritance.Tests.main ] [] From 7892144b3296edb7d575eee8e4d309fc6bab45ae Mon Sep 17 00:00:00 2001 From: HLWeil Date: Tue, 13 Aug 2024 16:58:25 +0200 Subject: [PATCH 09/20] add Fable-Compatible PropertyHelper class --- src/DynamicObj/DynamicObj.fs | 15 +-- src/DynamicObj/DynamicObj.fsproj | 1 + src/DynamicObj/PropertyHelper.fs | 31 ++++++ src/DynamicObj/ReflectionUtils.fs | 121 ++++++++++++++++++++--- tests/DynamicObject.Tests/Inheritance.fs | 22 ++++- tests/playground.fsx | 52 ++++++++++ 6 files changed, 223 insertions(+), 19 deletions(-) create mode 100644 src/DynamicObj/PropertyHelper.fs create mode 100644 tests/playground.fsx diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index 7cd8598..0790ba1 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -52,10 +52,13 @@ type DynamicObj() = /// Sets property value, creating a new property if none exists member this.SetValue (name,value) = // private // first check to see if there's a native property to set - - match ReflectionUtils.trySetPropertyValue this name value with - | Some _ -> - () + + match ReflectionUtils.tryGetPropertyInfo this name with + | Some pi -> + if pi.IsMutable then + pi.SetValue this value + else + failwith $"Cannot set value for static, immutable property \"{name}\"" | None -> // Next check the Properties collection for member match properties.TryGetValue name with @@ -78,8 +81,8 @@ type DynamicObj() = #else seq [ if includeInstanceProperties then - for prop in ReflectionUtils.getPublicProperties (this.GetType()) -> - new KeyValuePair(prop.Name, prop.GetValue(this, null)) + for prop in ReflectionUtils.getStaticProperties (this) -> + new KeyValuePair(prop.Name, prop.GetValue(this)) for key in properties.Keys -> new KeyValuePair(key, properties.[key]); ] diff --git a/src/DynamicObj/DynamicObj.fsproj b/src/DynamicObj/DynamicObj.fsproj index 80ae690..d63452c 100644 --- a/src/DynamicObj/DynamicObj.fsproj +++ b/src/DynamicObj/DynamicObj.fsproj @@ -24,6 +24,7 @@ + diff --git a/src/DynamicObj/PropertyHelper.fs b/src/DynamicObj/PropertyHelper.fs new file mode 100644 index 0000000..fda018a --- /dev/null +++ b/src/DynamicObj/PropertyHelper.fs @@ -0,0 +1,31 @@ +namespace DynamicObj + +open System.Reflection + +type PropertyHelper = + + { + Name : string + IsStatic : bool + IsDynamic : bool + IsMutable : bool + IsImmutable : bool + GetValue : obj -> obj + SetValue : obj -> obj -> unit + } + + #if !FABLE_COMPILER + + static member fromPropertyInfo (pI : PropertyInfo) = + { + Name = pI.Name + IsStatic = true + IsDynamic = false + IsMutable = pI.CanWrite + IsImmutable = not pI.CanWrite + GetValue = fun(o) -> pI.GetValue(o) + SetValue = fun o v -> pI.SetValue(o, v) + } + + #endif + diff --git a/src/DynamicObj/ReflectionUtils.fs b/src/DynamicObj/ReflectionUtils.fs index 4709c5c..bdf74a9 100644 --- a/src/DynamicObj/ReflectionUtils.fs +++ b/src/DynamicObj/ReflectionUtils.fs @@ -2,20 +2,122 @@ open Fable.Core +module FableJS = + + module PropertyDescriptor = + + [] + let tryGetPropertyValue (o:obj) (propName:string) : obj option = + jsNative + + let tryGetIsWritable (o:obj) : bool option = + tryGetPropertyValue o "writable" + |> Option.map (fun v -> v :?> bool) + + let containsGetter (o:obj) : bool = + match tryGetPropertyValue o "get" with + | Some _ -> true + | None -> false + + let containsSetter (o:obj) : bool = + match tryGetPropertyValue o "set" with + | Some _ -> true + | None -> false + + let isWritable (o:obj) : bool = + match tryGetIsWritable o with + | Some v -> v + | None -> containsSetter o + + [] + let getOwnPropertyNames (o:obj) : string [] = + jsNative + + [] + let getPrototype (o:obj) : obj = + jsNative + + let getStaticPropertyNames (o:obj) = + getPrototype o + |> getOwnPropertyNames + + [] + let setPropertyValue (o:obj) (propName:string) (value:obj) = + jsNative + + let createSetter (propName:string) = + fun (o:obj) (value:obj) -> + setPropertyValue o propName value + + [] + let getPropertyValue (o:obj) (propName:string) = + jsNative + + let createGetter (propName:string) = + fun (o:obj) -> + getPropertyValue o propName + + [] + let getPropertyDescriptor (o:obj) (propName:string) = + jsNative + + let getStaticPropertyHelpers (o:obj) : PropertyHelper [] = + getStaticPropertyNames o + |> Array.map (fun n -> + let pd = getPropertyDescriptor o n + let isWritable = PropertyDescriptor.isWritable pd + { + Name = n + IsStatic = true + IsDynamic = false + IsMutable = isWritable + IsImmutable = not isWritable + GetValue = createGetter n + SetValue = createSetter n + } + ) + + let getDynamicPropertyHelpers (o:obj) : PropertyHelper [] = + getOwnPropertyNames o + |> Array.map (fun n -> + let pd = getPropertyDescriptor o n + let isWritable = PropertyDescriptor.isWritable pd + { + Name = n + IsStatic = false + IsDynamic = true + IsMutable = isWritable + IsImmutable = not isWritable + GetValue = createGetter n + SetValue = createSetter n + } + ) + + [] + let getStaticProperties (o:obj) = + jsNative + module ReflectionUtils = open System open System.Reflection - #if !FABLE_COMPILER - - // Gets public properties including interface propterties - let getPublicProperties (t:Type) = + // Gets public, static properties including interface propterties + let getStaticProperties (o : obj) = + let t = o.GetType() [| for propInfo in t.GetProperties() -> propInfo for i in t.GetInterfaces() do yield! i.GetProperties() |] + |> Array.map PropertyHelper.fromPropertyInfo + + /// Try to get the PropertyInfo by name using reflection + let tryGetPropertyInfo (o:obj) (propName:string) = + getStaticProperties (o) + |> Array.tryFind (fun n -> n.Name = propName) + + #if !FABLE_COMPILER /// Creates an instance of the Object according to applyStyle and applies the function.. let buildApply (applyStyle:'a -> 'a) = let instance = @@ -40,11 +142,6 @@ module ReflectionUtils = | Microsoft.FSharp.Quotations.Patterns.PropertyGet (_,pInfo,_) -> Some pInfo.Name | _ -> None - /// Try to get the PropertyInfo by name using reflection - let tryGetPropertyInfo (o:obj) (propName:string) = - getPublicProperties (o.GetType()) - |> Array.tryFind (fun n -> n.Name = propName) - #endif /// Sets property value using reflection @@ -60,7 +157,7 @@ module ReflectionUtils = match tryGetPropertyInfo o propName with | Some property -> try - property.SetValue(o, value, null) + property.SetValue o value Some o with | :? System.ArgumentException -> None @@ -78,7 +175,7 @@ module ReflectionUtils = #else try match tryGetPropertyInfo o propName with - | Some v -> Some (v.GetValue(o,null)) + | Some v -> Some (v.GetValue(o)) | None -> None with | :? System.Reflection.TargetInvocationException -> None @@ -124,7 +221,7 @@ module ReflectionUtils = match tryGetPropertyInfo o propName with | Some property -> try - property.SetValue(o, null, null) + property.SetValue o null true with | :? System.ArgumentException -> false diff --git a/tests/DynamicObject.Tests/Inheritance.fs b/tests/DynamicObject.Tests/Inheritance.fs index 10aafc5..30b89c6 100644 --- a/tests/DynamicObject.Tests/Inheritance.fs +++ b/tests/DynamicObject.Tests/Inheritance.fs @@ -16,6 +16,21 @@ type Person(name : string) = with get() = name and set(value) = name <- value +[] +type PersonImmutable(name : string) = + + inherit DynamicObj() + + member this.Name + with get() = name + +[] +type Animal(name : string) = + + inherit DynamicObj() + + member val Name = name with get, set + let tests_set = testList "Set" [ testCase "Static Property" <| fun _ -> @@ -24,6 +39,11 @@ let tests_set = testList "Set" [ Expect.equal p.Name "Jane" "Static property should be set" Expect.equal (p.TryGetValue("Name")) (Some "Jane") "Static property should be retreivable dynamically" + testCase "Static Immutable Property" <| fun _ -> + let p = PersonImmutable("John") + let f = fun () -> p.SetValue("Name", "Jane") + Expect.throws f "Cannot set static property" + testCase "Dynamic Property" <| fun _ -> let p = Person("John") p.SetValue("Age", 42) @@ -64,7 +84,7 @@ let tests_remove = testList "Remove" [ p.Remove("Name") |> ignore - Expect.equal p.Name null "Static property should not be removed" + Expect.equal p.Name null "Static property should " testCase "Remove Dynamic" <| fun _ -> diff --git a/tests/playground.fsx b/tests/playground.fsx new file mode 100644 index 0000000..003db41 --- /dev/null +++ b/tests/playground.fsx @@ -0,0 +1,52 @@ +#r @"C:\Users\HLWei\source\repos\other\DynamicObj\tests\DynamicObject.Tests\bin\Release\net6.0\Fable.Pyxpecto.dll" +#r @"C:\Users\HLWei\source\repos\other\DynamicObj\tests\DynamicObject.Tests\bin\Release\net6.0\Fable.Core.dll" +#r @"C:\Users\HLWei\source\repos\other\DynamicObj\tests\DynamicObject.Tests\bin\Release\net6.0\Fable.Python.dll" +#r @"C:\Users\HLWei\source\repos\other\DynamicObj\tests\DynamicObject.Tests\bin\Release\net6.0\Microsoft.TestPlatform.Utilities.dll" +#r @"C:\Users\HLWei\source\repos\other\DynamicObj\tests\DynamicObject.Tests\bin\Release\net6.0\Microsoft.TestPlatform.CommunicationUtilities.dll" +#r @"C:\Users\HLWei\source\repos\other\DynamicObj\tests\DynamicObject.Tests\bin\Release\net6.0\Newtonsoft.Json.dll" +#r @"C:\Users\HLWei\source\repos\other\DynamicObj\tests\DynamicObject.Tests\bin\Release\net6.0\Microsoft.VisualStudio.CodeCoverage.Shim.dll" +#r @"C:\Users\HLWei\source\repos\other\DynamicObj\tests\DynamicObject.Tests\bin\Release\net6.0\DynamicObject.Tests.dll" +#r @"C:\Users\HLWei\source\repos\other\DynamicObj\tests\DynamicObject.Tests\bin\Release\net6.0\DynamicObj.dll" + +open DynamicObj + +//let a = DynamicObj () + +//a.SetValue("aaa", 5) + +//a.GetHashCode() + +//ReflectionUtils.getStaticProperties (a) + +//let o = a + +//let t = o.GetType() +//[| +// for propInfo in t.GetProperties() -> propInfo +// for i in t.GetInterfaces() do yield! i.GetProperties() +//|] +//|> Array.map PropertyHelper.fromPropertyInfo + + + + + + +type Person(name : string) = + + inherit DynamicObj() + + let mutable name = name + + member this.Name + with get() = name + //and set(value) = name <- value + +let p = Person("John") +p.SetValue("Name", "Jane") + +p.TryGetValue("Name") + +ReflectionUtils.tryGetPropertyValue p "Name" + +ReflectionUtils.tryGetPropertyInfo p "Name" \ No newline at end of file From 153b4901e6ee6dcf1166c0e7ddacbda7e11ee509 Mon Sep 17 00:00:00 2001 From: Heinrich Lukas Weil Date: Wed, 14 Aug 2024 11:38:13 +0200 Subject: [PATCH 10/20] improve and test Fable-compatability of reflectionutils --- src/DynamicObj/DynamicObj.fs | 4 +- src/DynamicObj/PropertyHelper.fs | 2 + src/DynamicObj/ReflectionUtils.fs | 184 +++++++++---------- tests/DynamicObject.Tests/ReflectionUtils.fs | 83 ++++++++- 4 files changed, 172 insertions(+), 101 deletions(-) diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index 0790ba1..d896111 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -66,7 +66,7 @@ type DynamicObj() = | _ -> properties.Add(name,value) member this.Remove name = - match ReflectionUtils.removeProperty this name with + match ReflectionUtils.removeStaticProperty this name with | true -> true // Maybe in map | false -> properties.Remove(name) @@ -133,4 +133,4 @@ type DynamicObj() = |> List.ofSeq |> List.sortBy (fun pair -> pair.Key) |> List.map (fun pair -> struct (pair.Key, pair.Value)) - |> (fun l -> l.GetHashCode()) \ No newline at end of file + |> fun l -> l.GetHashCode() \ No newline at end of file diff --git a/src/DynamicObj/PropertyHelper.fs b/src/DynamicObj/PropertyHelper.fs index fda018a..9c5f564 100644 --- a/src/DynamicObj/PropertyHelper.fs +++ b/src/DynamicObj/PropertyHelper.fs @@ -12,6 +12,7 @@ type PropertyHelper = IsImmutable : bool GetValue : obj -> obj SetValue : obj -> obj -> unit + RemoveValue : obj -> unit } #if !FABLE_COMPILER @@ -25,6 +26,7 @@ type PropertyHelper = IsImmutable = not pI.CanWrite GetValue = fun(o) -> pI.GetValue(o) SetValue = fun o v -> pI.SetValue(o, v) + RemoveValue = fun o -> pI.SetValue(o, null) } #endif diff --git a/src/DynamicObj/ReflectionUtils.fs b/src/DynamicObj/ReflectionUtils.fs index bdf74a9..6d25e51 100644 --- a/src/DynamicObj/ReflectionUtils.fs +++ b/src/DynamicObj/ReflectionUtils.fs @@ -33,13 +33,14 @@ module FableJS = let getOwnPropertyNames (o:obj) : string [] = jsNative - [] + [] let getPrototype (o:obj) : obj = jsNative let getStaticPropertyNames (o:obj) = getPrototype o |> getOwnPropertyNames + |> Array.filter (fun n -> n <> "constructor") [] let setPropertyValue (o:obj) (propName:string) (value:obj) = @@ -49,6 +50,22 @@ module FableJS = fun (o:obj) (value:obj) -> setPropertyValue o propName value + let removeStaticPropertyValue (o:obj) (propName:string) = + setPropertyValue o propName null + + [] + let deleteDynamicPropertyValue (o:obj) (propName:string) = + jsNative + + let createRemover (propName:string) (isStatic : bool) = + if isStatic then + fun (o:obj) -> + removeStaticPropertyValue o propName + else + fun (o:obj) -> + deleteDynamicPropertyValue o propName + + [] let getPropertyValue (o:obj) (propName:string) = jsNative @@ -61,10 +78,13 @@ module FableJS = let getPropertyDescriptor (o:obj) (propName:string) = jsNative + let getStaticPropertyDescriptor (o:obj) (propName:string) = + getPropertyDescriptor (getPrototype o) propName + let getStaticPropertyHelpers (o:obj) : PropertyHelper [] = getStaticPropertyNames o |> Array.map (fun n -> - let pd = getPropertyDescriptor o n + let pd = getStaticPropertyDescriptor o n let isWritable = PropertyDescriptor.isWritable pd { Name = n @@ -74,6 +94,7 @@ module FableJS = IsImmutable = not isWritable GetValue = createGetter n SetValue = createSetter n + RemoveValue = createRemover n true } ) @@ -90,6 +111,7 @@ module FableJS = IsImmutable = not isWritable GetValue = createGetter n SetValue = createSetter n + RemoveValue = createRemover n false } ) @@ -97,6 +119,7 @@ module FableJS = let getStaticProperties (o:obj) = jsNative + module ReflectionUtils = open System @@ -104,18 +127,59 @@ module ReflectionUtils = // Gets public, static properties including interface propterties let getStaticProperties (o : obj) = - let t = o.GetType() - [| - for propInfo in t.GetProperties() -> propInfo - for i in t.GetInterfaces() do yield! i.GetProperties() - |] - |> Array.map PropertyHelper.fromPropertyInfo - - /// Try to get the PropertyInfo by name using reflection + #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT + FableJS.getStaticPropertyHelpers o + #else + let t = o.GetType() + [| + for propInfo in t.GetProperties() -> propInfo + for i in t.GetInterfaces() do yield! i.GetProperties() + |] + |> Array.map PropertyHelper.fromPropertyInfo + #endif + + /// Try to get the PropertyInfo by name using reflection let tryGetPropertyInfo (o:obj) (propName:string) = getStaticProperties (o) |> Array.tryFind (fun n -> n.Name = propName) + let trySetPropertyValue (o:obj) (propName:string) (value:obj) = + match tryGetPropertyInfo o propName with + | Some property when property.IsMutable -> + property.SetValue o value + true + | _ -> false + + let tryGetPropertyValue (o:obj) (propName:string) = + try + match tryGetPropertyInfo o propName with + | Some v -> Some (v.GetValue(o)) + | None -> None + with + | :? System.Reflection.TargetInvocationException -> None + | :? System.NullReferenceException -> None + + + /// Gets property value as 'a option using reflection. Cast to 'a + let tryGetPropertyValueAs<'a> (o:obj) (propName:string) = + try + tryGetPropertyValue o propName + |> Option.map (fun v -> v :?> 'a) + + with + | :? System.Reflection.TargetInvocationException -> None + | :? System.NullReferenceException -> None + + let removeStaticProperty (o:obj) (propName:string) = + + match tryGetPropertyInfo o propName with + | Some property when property.IsMutable -> + property.RemoveValue(o) + true + | _ -> false + + + #if !FABLE_COMPILER /// Creates an instance of the Object according to applyStyle and applies the function.. @@ -142,89 +206,21 @@ module ReflectionUtils = | Microsoft.FSharp.Quotations.Patterns.PropertyGet (_,pInfo,_) -> Some pInfo.Name | _ -> None - #endif + /// Updates property value by given function + let tryUpdatePropertyValueFromName (o:obj) (propName:string) (f: 'a -> 'a) = + let v = optBuildApply f (tryGetPropertyValueAs<'a> o propName) + trySetPropertyValue o propName v + //o - /// Sets property value using reflection - #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT - [] - #endif - let trySetPropertyValue (o:obj) (propName:string) (value:obj) = - - #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT - jsNative - #else + /// Updates property value by given function + let tryUpdatePropertyValue (o:obj) (expr : Microsoft.FSharp.Quotations.Expr) (f: 'a -> 'a) = + let propName = tryGetPropertyName expr + let g = (tryGetPropertyValueAs<'a> o propName.Value) + let v = optBuildApply f g + trySetPropertyValue o propName.Value v + //o - match tryGetPropertyInfo o propName with - | Some property -> - try - property.SetValue o value - Some o - with - | :? System.ArgumentException -> None - | :? System.NullReferenceException -> None - | None -> None - #endif - - /// Gets property value as option using reflection - #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT - [] - #endif - let tryGetPropertyValue (o:obj) (propName:string) = - #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT - jsNative - #else - try - match tryGetPropertyInfo o propName with - | Some v -> Some (v.GetValue(o)) - | None -> None - with - | :? System.Reflection.TargetInvocationException -> None - | :? System.NullReferenceException -> None - #endif - - ///// Gets property value as 'a option using reflection. Cast to 'a - //let tryGetPropertyValueAs<'a> (o:obj) (propName:string) = - // try - // tryGetPropertyValue o propName - // |> Option.map (fun v -> v :?> 'a) - - // with - // | :? System.Reflection.TargetInvocationException -> None - // | :? System.NullReferenceException -> None - - ///// Updates property value by given function - //let tryUpdatePropertyValueFromName (o:obj) (propName:string) (f: 'a -> 'a) = - // let v = optBuildApply f (tryGetPropertyValueAs<'a> o propName) - // trySetPropertyValue o propName v - // //o - - ///// Updates property value by given function - //let tryUpdatePropertyValue (o:obj) (expr : Microsoft.FSharp.Quotations.Expr) (f: 'a -> 'a) = - // let propName = tryGetPropertyName expr - // let g = (tryGetPropertyValueAs<'a> o propName.Value) - // let v = optBuildApply f g - // trySetPropertyValue o propName.Value v - // //o - - //let updatePropertyValueAndIgnore (o:obj) (expr : Microsoft.FSharp.Quotations.Expr) (f: 'a -> 'a) = - // tryUpdatePropertyValue o expr f |> ignore - - - /// Sets property value using reflection - #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT - [] - #endif - let removeProperty (o:obj) (propName:string) = - #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT - jsNative - #else - match tryGetPropertyInfo o propName with - | Some property -> - try - property.SetValue o null - true - with - | :? System.ArgumentException -> false - | :? System.NullReferenceException -> false - | None -> false - #endif + let updatePropertyValueAndIgnore (o:obj) (expr : Microsoft.FSharp.Quotations.Expr) (f: 'a -> 'a) = + tryUpdatePropertyValue o expr f |> ignore + + #endif \ No newline at end of file diff --git a/tests/DynamicObject.Tests/ReflectionUtils.fs b/tests/DynamicObject.Tests/ReflectionUtils.fs index c4f7f02..939abe2 100644 --- a/tests/DynamicObject.Tests/ReflectionUtils.fs +++ b/tests/DynamicObject.Tests/ReflectionUtils.fs @@ -3,14 +3,87 @@ module ReflectionUtils.Tests open System open Fable.Pyxpecto open DynamicObj +open Fable.Core +[] +type TestObject(id : string, name : string) = -let tests_baseObject = testList "Dynamic Set" [ - testCase "Same String" <| fun _ -> - //ReflectionUtils.tryGetPropertyValue - Expect.isTrue true "daw" + let id = id + let mutable name = name + + member this.Id + with get() = id + + member this.Name + with get() = name + and set(value) = name <- value + + +let tests_PropertyHelper = testList "PropertyHelper" [ + testCase "getStaticProperties" <| fun _ -> + let p = TestObject("1", "test") + let helpers = ReflectionUtils.getStaticProperties p + Expect.hasLength helpers 2 "Should have 2 properties" + let idOption = Array.tryFind (fun h -> h.Name = "Id") helpers + let id = Expect.wantSome idOption "Should have Id property" + Expect.equal id.IsStatic true "Id should be static" + Expect.equal id.IsDynamic false "Id should not be dynamic" + Expect.equal id.IsMutable false "Id should not be mutable" + Expect.equal id.IsImmutable true "Id should be immutable" + let nameOption = Array.tryFind (fun h -> h.Name = "Name") helpers + let name = Expect.wantSome nameOption "Should have Name property" + Expect.equal name.IsStatic true "Name should be static" + Expect.equal name.IsDynamic false "Name should not be dynamic" + Expect.equal name.IsMutable true "Name should be mutable" + Expect.equal name.IsImmutable false "Name should not be immutable" +] + +let tests_TryGetPropertyValue = testList "TryGetPropertyValue" [ + testCase "existing" <| fun _ -> + let p = TestObject("1", "test") + let nameOption = ReflectionUtils.tryGetPropertyValue p "Name" + let name = Expect.wantSome nameOption "Should have mutable value" + Expect.equal name "test" "Should have correct mutable value" + + let idOption = ReflectionUtils.tryGetPropertyValue p "Id" + let id = Expect.wantSome idOption "Should have immutable value" + Expect.equal id "1" "Should have correct immutable value" + + testCase "non-existing" <| fun _ -> + let p = TestObject("1", "test") + let option = ReflectionUtils.tryGetPropertyValue p "NonExisting" + Expect.equal option None "Should not have value" +] + +let tests_TrySetPropertyValue = testList "TrySetPropertyValue" [ + testCase "mutable" <| fun _ -> + let p = TestObject("1", "test") + let wasSet = ReflectionUtils.trySetPropertyValue p "Name" "newName" + Expect.isTrue wasSet "Should have set value" + let nameOption = ReflectionUtils.tryGetPropertyValue p "Name" + let name = Expect.wantSome nameOption "Should have mutable value" + Expect.equal name "newName" "Should have correct mutable value" + + testCase "immutable" <| fun _ -> + let p = TestObject("1", "test") + let wasSet = ReflectionUtils.trySetPropertyValue p "Id" "newId" + Expect.isFalse wasSet "Should not have set value" + + let idOption = ReflectionUtils.tryGetPropertyValue p "Id" + let id = Expect.wantSome idOption "Should have immutable value" + Expect.equal id "1" "Should have correct immutable value" + + testCase "non-existing" <| fun _ -> + let p = TestObject("1", "test") + let wasSet = ReflectionUtils.trySetPropertyValue p "address" "newAddress" + Expect.isFalse wasSet "Should not have set value" + + let addressOption = ReflectionUtils.tryGetPropertyValue p "address" + Expect.isNone addressOption "Should not have value" ] let main = testList "ReflectionUtils" [ - tests_baseObject + tests_PropertyHelper + tests_TryGetPropertyValue + tests_TrySetPropertyValue ] \ No newline at end of file From 44651810d47d60a7f54f4f0cea88ddd1060405b7 Mon Sep 17 00:00:00 2001 From: Heinrich Lukas Weil Date: Wed, 14 Aug 2024 15:00:56 +0200 Subject: [PATCH 11/20] fix up basic functionality for javascript --- build/ProjectInfo.fs | 5 ++ build/TestTasks.fs | 4 +- src/DynamicObj/DynamicObj.fs | 28 +++++-- src/DynamicObj/DynamicObj.fsproj | 1 + src/DynamicObj/HashCodes.fs | 46 +++++++++++ src/DynamicObj/ReflectionUtils.fs | 80 +++++++++++++------- tests/DynamicObject.Tests/DynamicObj.fs | 15 ++-- tests/DynamicObject.Tests/ReflectionUtils.fs | 22 ++++++ 8 files changed, 158 insertions(+), 43 deletions(-) create mode 100644 src/DynamicObj/HashCodes.fs diff --git a/build/ProjectInfo.fs b/build/ProjectInfo.fs index 4c8ee68..0418ce6 100644 --- a/build/ProjectInfo.fs +++ b/build/ProjectInfo.fs @@ -7,6 +7,11 @@ let project = "DynamicObj" let summary = "F# library supporting Dynamic Objects including inheritance in functional style." +let fableTestProjects = + [ + "tests/DynamicObject.Tests" + ] + let testProjects = [ "tests/DynamicObject.Tests" diff --git a/build/TestTasks.fs b/build/TestTasks.fs index 8d3f615..70b07ad 100644 --- a/build/TestTasks.fs +++ b/build/TestTasks.fs @@ -20,7 +20,7 @@ module RunTests = //} let runTestsJs = BuildTask.create "runTestsJS" [clean; build] { - for path in ProjectInfo.testProjects do + for path in ProjectInfo.fableTestProjects do // transpile js files from fsharp code run dotnet $"fable {path} -o {path}/js" "" // run mocha in target path to execute tests @@ -38,7 +38,7 @@ module RunTests = //} let runTestsPy = BuildTask.create "runTestsPy" [clean; build] { - for path in ProjectInfo.testProjects do + for path in ProjectInfo.fableTestProjects do //transpile py files from fsharp code run dotnet $"fable {path} -o {path}/py --lang python" "" // run pyxpecto in target path to execute tests in python diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index d896111..786b9c3 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -60,10 +60,14 @@ type DynamicObj() = else failwith $"Cannot set value for static, immutable property \"{name}\"" | None -> + #if FABLE_COMPILER + FableJS.setPropertyValue this name value + #else // Next check the Properties collection for member match properties.TryGetValue name with | true,_ -> properties.[name] <- value | _ -> properties.Add(name,value) + #endif member this.Remove name = match ReflectionUtils.removeStaticProperty this name with @@ -74,10 +78,16 @@ type DynamicObj() = /// Returns both instance and dynamic properties when passed true, only dynamic properties otherwise. /// Properties are returned as a key value pair of the member names and the boxed values - member this.GetProperties includeInstanceProperties = - #if FABLE_COMPILER - Fable.getPropertyNames this - |> Seq.map (fun name -> new KeyValuePair(name, this.TryGetValue name)) + member this.GetProperties includeInstanceProperties : seq> = + #if FABLE_COMPILER + FableJS.getPropertyHelpers this + |> Seq.choose (fun pd -> + if includeInstanceProperties || pd.IsDynamic then + new KeyValuePair(pd.Name, pd.GetValue this) + |> Some + else + None + ) #else seq [ if includeInstanceProperties then @@ -130,7 +140,9 @@ type DynamicObj() = override this.GetHashCode () = this.GetProperties(true) - |> List.ofSeq - |> List.sortBy (fun pair -> pair.Key) - |> List.map (fun pair -> struct (pair.Key, pair.Value)) - |> fun l -> l.GetHashCode() \ No newline at end of file + |> Seq.map (fun kv -> + kv + ) + |> Seq.sortBy (fun pair -> pair.Key) + |> HashCodes.boxHashKeyValSeq + |> fun x -> x :?> int \ No newline at end of file diff --git a/src/DynamicObj/DynamicObj.fsproj b/src/DynamicObj/DynamicObj.fsproj index d63452c..68092f6 100644 --- a/src/DynamicObj/DynamicObj.fsproj +++ b/src/DynamicObj/DynamicObj.fsproj @@ -24,6 +24,7 @@ + diff --git a/src/DynamicObj/HashCodes.fs b/src/DynamicObj/HashCodes.fs new file mode 100644 index 0000000..96bd4b4 --- /dev/null +++ b/src/DynamicObj/HashCodes.fs @@ -0,0 +1,46 @@ +module DynamicObj.HashCodes + +let mergeHashes (hash1 : int) (hash2 : int) : int = + 0x9e3779b9 + hash2 + (hash1 <<< 6) + (hash1 >>> 2) + +let hashDateTime (dt : System.DateTime) : int = + let mutable acc = 0 + acc <- mergeHashes acc dt.Year + acc <- mergeHashes acc dt.Month + acc <- mergeHashes acc dt.Day + acc <- mergeHashes acc dt.Hour + acc <- mergeHashes acc dt.Minute + acc <- mergeHashes acc dt.Second + acc + + +let hash obj = + obj.GetHashCode() + +let boxHashOption (a: 'a option) : obj = + if a.IsSome then a.Value.GetHashCode() else (0).GetHashCode() + |> box + +let boxHashArray (a: 'a []) : obj = + a + // from https://stackoverflow.com/a/53507559 + |> Array.fold (fun acc o -> + hash o + |> mergeHashes acc) 0 + |> box + +let boxHashSeq (a: seq<'a>) : obj = + a + // from https://stackoverflow.com/a/53507559 + |> Seq.fold (fun acc o -> + hash o + |> mergeHashes acc) 0 + |> box + +let boxHashKeyValSeq (a: seq>) : obj = + a + // from https://stackoverflow.com/a/53507559 + |> Seq.fold (fun acc o -> + mergeHashes (hash o.Key) (hash o.Value) + |> mergeHashes acc) 0 + |> box \ No newline at end of file diff --git a/src/DynamicObj/ReflectionUtils.fs b/src/DynamicObj/ReflectionUtils.fs index 6d25e51..49a1ca6 100644 --- a/src/DynamicObj/ReflectionUtils.fs +++ b/src/DynamicObj/ReflectionUtils.fs @@ -29,6 +29,15 @@ module FableJS = | Some v -> v | None -> containsSetter o + [] + let valueIsFunction (o:obj) : bool = + jsNative + + let isFunction (o:obj) : bool = + match tryGetPropertyValue o "value" with + | Some v -> valueIsFunction v + | None -> false + [] let getOwnPropertyNames (o:obj) : string [] = jsNative @@ -83,41 +92,54 @@ module FableJS = let getStaticPropertyHelpers (o:obj) : PropertyHelper [] = getStaticPropertyNames o - |> Array.map (fun n -> + |> Array.choose (fun n -> let pd = getStaticPropertyDescriptor o n - let isWritable = PropertyDescriptor.isWritable pd - { - Name = n - IsStatic = true - IsDynamic = false - IsMutable = isWritable - IsImmutable = not isWritable - GetValue = createGetter n - SetValue = createSetter n - RemoveValue = createRemover n true - } + if PropertyDescriptor.isFunction pd then + None + else + let isWritable = PropertyDescriptor.isWritable pd + { + Name = n + IsStatic = true + IsDynamic = false + IsMutable = isWritable + IsImmutable = not isWritable + GetValue = createGetter n + SetValue = createSetter n + RemoveValue = createRemover n true + } + |> Some ) let getDynamicPropertyHelpers (o:obj) : PropertyHelper [] = getOwnPropertyNames o - |> Array.map (fun n -> + |> Array.choose (fun n -> let pd = getPropertyDescriptor o n - let isWritable = PropertyDescriptor.isWritable pd - { - Name = n - IsStatic = false - IsDynamic = true - IsMutable = isWritable - IsImmutable = not isWritable - GetValue = createGetter n - SetValue = createSetter n - RemoveValue = createRemover n false - } + if PropertyDescriptor.isFunction pd then + None + else + let isWritable = PropertyDescriptor.isWritable pd + { + Name = n + IsStatic = false + IsDynamic = true + IsMutable = isWritable + IsImmutable = not isWritable + GetValue = createGetter n + SetValue = createSetter n + RemoveValue = createRemover n false + } + |> Some ) - [] - let getStaticProperties (o:obj) = - jsNative + let getPropertyHelpers (o:obj) = + getStaticPropertyHelpers o + |> Array.append (getDynamicPropertyHelpers o) + + let getPropertyNames (o:obj) = + getPropertyHelpers o + |> Array.map (fun h -> h.Name) + module ReflectionUtils = @@ -140,7 +162,11 @@ module ReflectionUtils = /// Try to get the PropertyInfo by name using reflection let tryGetPropertyInfo (o:obj) (propName:string) = + #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT + FableJS.getPropertyHelpers o + #else getStaticProperties (o) + #endif |> Array.tryFind (fun n -> n.Name = propName) let trySetPropertyValue (o:obj) (propName:string) (value:obj) = diff --git a/tests/DynamicObject.Tests/DynamicObj.fs b/tests/DynamicObject.Tests/DynamicObj.fs index 3ca36ab..7c180ab 100644 --- a/tests/DynamicObject.Tests/DynamicObj.fs +++ b/tests/DynamicObject.Tests/DynamicObj.fs @@ -30,7 +30,7 @@ let tests_set = testList "Set" [ Expect.notEqual a b "Values should not be equal" Expect.notEqual b a "Values should not be equal (Reversed equality)" - testCase "Same list" <| fun _ -> + testCase "Same lists different keys" <| fun _ -> let a' = DynamicObj () let b' = DynamicObj () a'.SetValue("quack!", [1; 2; 3]) @@ -169,17 +169,20 @@ let tests_formatString = testList "FormatString" [ testCase "Format string 1" <| fun _ -> let foo = DynamicObj() - foo.SetValue("bar", [1;2;3;4]) - let expected = "?bar: [1; 2; 3; ... ]" + let list = [1;2;3;4] + foo.SetValue("bar", list) + let expected = $"?bar: {list}" Expect.equal (foo |> DynObj.format) expected "Format string 1 failed" testCase "Format string 2" <| fun _ -> let foo = DynamicObj() - foo.SetValue("corgi", "corgi") + let corgi = "corgi" + foo.SetValue("corgi", corgi) let inner = DynamicObj() - inner.SetValue("bar", "baz") + let baz = "baz" + inner.SetValue("bar", baz) foo.SetValue("foo", inner) - let expected = $"""?corgi: corgi{Environment.NewLine}?foo:{Environment.NewLine} ?bar: baz""" + let expected = $"""?corgi: {corgi}{Environment.NewLine}?foo:{Environment.NewLine} ?bar: {baz}""" Expect.equal (foo |> DynObj.format) expected "Format string 2 failed" ] diff --git a/tests/DynamicObject.Tests/ReflectionUtils.fs b/tests/DynamicObject.Tests/ReflectionUtils.fs index 939abe2..6667721 100644 --- a/tests/DynamicObject.Tests/ReflectionUtils.fs +++ b/tests/DynamicObject.Tests/ReflectionUtils.fs @@ -36,8 +36,30 @@ let tests_PropertyHelper = testList "PropertyHelper" [ Expect.equal name.IsDynamic false "Name should not be dynamic" Expect.equal name.IsMutable true "Name should be mutable" Expect.equal name.IsImmutable false "Name should not be immutable" + + testCase "TryGetPropertyInfo" <| fun _ -> + let p = TestObject("1", "test") + let idOption = ReflectionUtils.tryGetPropertyInfo p "Id" + let id = Expect.wantSome idOption "Should have immutable property" + Expect.equal id.Name "Id" "Should have correct property" + Expect.equal id.IsStatic true "Id should be static" + Expect.equal id.IsDynamic false "Id should not be dynamic" + Expect.equal id.IsMutable false "Id should not be mutable" + Expect.equal id.IsImmutable true "Id should be immutable" + + let nameOption = ReflectionUtils.tryGetPropertyInfo p "Name" + let name = Expect.wantSome nameOption "Should have mutable property" + Expect.equal name.Name "Name" "Should have correct property" + Expect.equal name.IsStatic true "Name should be static" + Expect.equal name.IsDynamic false "Name should not be dynamic" + Expect.equal name.IsMutable true "Name should be mutable" + Expect.equal name.IsImmutable false "Name should not be immutable" + + let nonExistingOption = ReflectionUtils.tryGetPropertyInfo p "NonExisting" + Expect.isNone nonExistingOption "Should not have property" ] + let tests_TryGetPropertyValue = testList "TryGetPropertyValue" [ testCase "existing" <| fun _ -> let p = TestObject("1", "test") From 38ae5595df0b35c45910fc87cac1cf09adf97179 Mon Sep 17 00:00:00 2001 From: Heinrich Lukas Weil Date: Wed, 14 Aug 2024 16:00:12 +0200 Subject: [PATCH 12/20] finish up fable javascript --- src/DynamicObj/DynObj.fs | 23 +++++----- src/DynamicObj/DynamicObj.fs | 25 +++++++++++ src/DynamicObj/ReflectionUtils.fs | 11 +++-- tests/DynamicObject.Tests/Inheritance.fs | 57 ++++-------------------- 4 files changed, 54 insertions(+), 62 deletions(-) diff --git a/src/DynamicObj/DynObj.fs b/src/DynamicObj/DynObj.fs index 41ae56a..78bcb0f 100644 --- a/src/DynamicObj/DynObj.fs +++ b/src/DynamicObj/DynObj.fs @@ -86,22 +86,23 @@ module DynObj = let format (d:DynamicObj) = - let members = d.GetPropertyNames(true) |> List.ofSeq + let members = d.GetPropertyHelpers(true) |> List.ofSeq - let rec loop (object:DynamicObj) (identationLevel:int) (membersLeft:string list) (acc:string list) = - let ident = [for i in 0 .. identationLevel-1 do yield " "] |> String.concat "" + let rec loop (object:DynamicObj) (indentationLevel:int) (membersLeft:PropertyHelper list) (acc:string list) = + let indent = [for i in 0 .. indentationLevel-1 do yield " "] |> String.concat "" match membersLeft with | [] -> acc |> List.rev |> String.concat System.Environment.NewLine | m::rest -> - let item = object.TryGetValue m + let item = m.GetValue object + let dynamicIndicator = if m.IsDynamic then "?" else "" + let name = m.Name match item with - | Some (:? DynamicObj as item) -> - let innerMembers = item.GetPropertyNames(true) |> Seq.cast |> List.ofSeq - let innerPrint = (loop item (identationLevel + 1) innerMembers []) - loop object identationLevel rest ($"{ident}?{m}:{System.Environment.NewLine}{innerPrint}" :: acc) - | Some item -> - loop object identationLevel rest ($"{ident}?{m}: {item}"::acc) - | None -> loop object identationLevel rest acc + | :? DynamicObj as item -> + let innerMembers = item.GetPropertyHelpers(true) |> List.ofSeq + let innerPrint = (loop item (indentationLevel + 1) innerMembers []) + loop object indentationLevel rest ($"{indent}{dynamicIndicator}{name}:{System.Environment.NewLine}{innerPrint}" :: acc) + | item -> + loop object indentationLevel rest ($"{indent}{dynamicIndicator}{name}: {item}"::acc) loop d 0 members [] diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index 786b9c3..47e5f0e 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -76,6 +76,31 @@ type DynamicObj() = | false -> properties.Remove(name) + member this.GetPropertyHelpers (includeInstanceProperties) = + #if FABLE_COMPILER + FableJS.getPropertyHelpers this + |> Seq.filter (fun pd -> + includeInstanceProperties || pd.IsDynamic + ) + #else + seq [ + if includeInstanceProperties then + yield! ReflectionUtils.getStaticProperties (this) + for key in properties.Keys -> + { + Name = key + IsStatic = false + IsDynamic = true + IsMutable = true + IsImmutable = false + GetValue = fun o -> properties.[key] + SetValue = fun o v -> properties.[key] <- v + RemoveValue = fun o -> properties.Remove(key) |> ignore + } + ] + #endif + |> Seq.filter (fun p -> p.Name.ToLower() <> "properties") + /// Returns both instance and dynamic properties when passed true, only dynamic properties otherwise. /// Properties are returned as a key value pair of the member names and the boxed values member this.GetProperties includeInstanceProperties : seq> = diff --git a/src/DynamicObj/ReflectionUtils.fs b/src/DynamicObj/ReflectionUtils.fs index 49a1ca6..1857267 100644 --- a/src/DynamicObj/ReflectionUtils.fs +++ b/src/DynamicObj/ReflectionUtils.fs @@ -111,11 +111,16 @@ module FableJS = |> Some ) + let transpiledPropertyRegex = "^[a-zA-Z]+@[0-9]+$" + + let isTranspiledPropertyHelper (propertyName : string) = + System.Text.RegularExpressions.Regex.IsMatch(propertyName, transpiledPropertyRegex) + let getDynamicPropertyHelpers (o:obj) : PropertyHelper [] = getOwnPropertyNames o |> Array.choose (fun n -> let pd = getPropertyDescriptor o n - if PropertyDescriptor.isFunction pd then + if PropertyDescriptor.isFunction pd || isTranspiledPropertyHelper n then None else let isWritable = PropertyDescriptor.isWritable pd @@ -133,8 +138,8 @@ module FableJS = ) let getPropertyHelpers (o:obj) = - getStaticPropertyHelpers o - |> Array.append (getDynamicPropertyHelpers o) + getDynamicPropertyHelpers o + |> Array.append (getStaticPropertyHelpers o) let getPropertyNames (o:obj) = getPropertyHelpers o diff --git a/tests/DynamicObject.Tests/Inheritance.fs b/tests/DynamicObject.Tests/Inheritance.fs index 30b89c6..215d22e 100644 --- a/tests/DynamicObject.Tests/Inheritance.fs +++ b/tests/DynamicObject.Tests/Inheritance.fs @@ -24,13 +24,6 @@ type PersonImmutable(name : string) = member this.Name with get() = name -[] -type Animal(name : string) = - - inherit DynamicObj() - - member val Name = name with get, set - let tests_set = testList "Set" [ testCase "Static Property" <| fun _ -> @@ -72,13 +65,6 @@ let tests_set = testList "Set" [ let tests_remove = testList "Remove" [ - ptestCase "Returns false on static" <| fun _ -> - let p = Person("John") - - let r = p.Remove("Name") - - Expect.isFalse r "Static property should not be removed" - testCase "Remove Static" <| fun _ -> let p = Person("John") @@ -86,7 +72,6 @@ let tests_remove = testList "Remove" [ Expect.equal p.Name null "Static property should " - testCase "Remove Dynamic" <| fun _ -> let p = Person("John") @@ -113,43 +98,20 @@ let tests_remove = testList "Remove" [ ] -let tests_formatString = ptestList "FormatString" [ +let tests_formatString = testList "FormatString" [ testCase "Format string 1" <| fun _ -> - let foo = DynamicObj() - foo.SetValue("bar", [1;2;3;4]) - let expected = "?bar: [1; 2; 3; ... ]" - Expect.equal (foo |> DynObj.format) expected "Format string 1 failed" - + + let name = "John" + let age = 20 + let p = Person("John") + p.SetValue("age", age) + let expected = $"Name: {name}{System.Environment.NewLine}?age: {age}" + Expect.equal (p |> DynObj.format) expected "Format string 1 failed" ] -let tests_combine = ptestList "Combine" [ - - testCase "Combine flat DOs" <| fun _ -> - let target = DynamicObj() - - target.SetValue("target-unique", [42]) - target.SetValue("will-be-overridden", "WAS_NOT_OVERRIDDEN!") - - let source = DynamicObj() - - source.SetValue("source-unique", [42; 32]) - source.SetValue("will-be-overridden", "WAS_OVERRIDDEN =)") - - let combined = DynObj.combine target source - - let expected = DynamicObj() - - expected.SetValue("target-unique", [42]) - expected.SetValue("source-unique", [42; 32]) - expected.SetValue("will-be-overridden", "WAS_OVERRIDDEN =)") - - Expect.equal expected combined "Combine flat DOs failed" - -] - -let tests_print = ptestList "Print" [ +let tests_print = testList "Print" [ testCase "Test Print For Issue 14" <| fun _ -> let outer = DynamicObj() @@ -172,5 +134,4 @@ let main = testList "Inheritance" [ tests_set tests_remove tests_formatString - tests_combine ] \ No newline at end of file From 01645ae11c1f7df8df8d2051eedf6e5a11a58d69 Mon Sep 17 00:00:00 2001 From: HLWeil Date: Thu, 15 Aug 2024 16:47:28 +0200 Subject: [PATCH 13/20] start implementing Fable Python --- .gitignore | 3 +- DynamicObj.sln | 1 + build/TestTasks.fs | 2 +- poetry.lock | 174 ++++++++++++++++++++++++++++++ pyproject.toml | 20 ++++ src/DynamicObj/DynamicObj.fs | 25 ++++- src/DynamicObj/DynamicObj.fsproj | 2 + src/DynamicObj/FableJS.fs | 148 +++++++++++++++++++++++++ src/DynamicObj/FablePy.fs | 149 +++++++++++++++++++++++++ src/DynamicObj/ReflectionUtils.fs | 152 ++------------------------ 10 files changed, 525 insertions(+), 151 deletions(-) create mode 100644 poetry.lock create mode 100644 pyproject.toml create mode 100644 src/DynamicObj/FableJS.fs create mode 100644 src/DynamicObj/FablePy.fs diff --git a/.gitignore b/.gitignore index 4155693..75f8e3a 100644 --- a/.gitignore +++ b/.gitignore @@ -196,4 +196,5 @@ docsrc/tools/FSharp.Formatting.svclog .ionide pkg /tests/**/js -/tests/**/py \ No newline at end of file +/tests/**/py +/.venv \ No newline at end of file diff --git a/DynamicObj.sln b/DynamicObj.sln index bec13c1..6b45628 100644 --- a/DynamicObj.sln +++ b/DynamicObj.sln @@ -25,6 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".proj", ".proj", "{C3CF2F15 key.snk = key.snk LICENSE = LICENSE package.json = package.json + pyproject.toml = pyproject.toml README.md = README.md RELEASE_NOTES.md = RELEASE_NOTES.md EndProjectSection diff --git a/build/TestTasks.fs b/build/TestTasks.fs index 70b07ad..360f9fb 100644 --- a/build/TestTasks.fs +++ b/build/TestTasks.fs @@ -51,7 +51,7 @@ module RunTests = |> Seq.iter dotnetRun } -let runTests = BuildTask.create "RunTests" [clean; build; RunTests.runTestsJs; (*RunTests.runTestsJsNative; *)(*RunTests.runTestsPy;*) (*RunTests.runTestsPyNative; *)RunTests.runTestsDotnet] { +let runTests = BuildTask.create "RunTests" [clean; build; RunTests.runTestsJs; (*RunTests.runTestsJsNative; *)RunTests.runTestsPy;(*RunTests.runTestsPyNative; *)RunTests.runTestsDotnet] { () } diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..5e4e520 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,174 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "certifi" +version = "2024.7.4" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, + {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, +] + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.6.0" +files = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "pytest" +version = "8.3.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "urllib3" +version = "1.26.19" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "urllib3-1.26.19-py2.py3-none-any.whl", hash = "sha256:37a0344459b199fce0e80b0d3569837ec6b6937435c5244e7fd73fa6006830f3"}, + {file = "urllib3-1.26.19.tar.gz", hash = "sha256:3e3d753a8618b86d7de333b4223005f68720bcd6a7d2bcb9fbd2229ec7c1e429"}, +] + +[package.extras] +brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.10" +content-hash = "b71d5769d70eac536444eb6d8a3f12e57d59113029b8de56c9bf24e9a8598dc8" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..33693a1 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,20 @@ +[tool.poetry] +name = "DynamicObj" +version = "3.0.0" +description = "Fable compatible library supporting Dynamic Objects including inheritance in functional style." +authors = ["Heinrich Lukas Weil ", "Kevin Frey "] +maintainers = ["Florian Wetzels"] +readme = "README.md" +repository = "https://github.com/nfdi4plants/ARCtrl" +keywords = ["arc", "annotated research context", "isa", "research data", "multi platform"] + +[tool.poetry.dependencies] +python = "^3.10" +requests = "2.28.1" + +[tool.poetry.group.dev.dependencies] +pytest = "^8.1.1" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" \ No newline at end of file diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index 47e5f0e..74c3d6e 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -60,8 +60,11 @@ type DynamicObj() = else failwith $"Cannot set value for static, immutable property \"{name}\"" | None -> - #if FABLE_COMPILER + #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT FableJS.setPropertyValue this name value + #endif + #if FABLE_COMPILER_PYTHON + FablePy.setPropertyValue this name value #else // Next check the Properties collection for member match properties.TryGetValue name with @@ -77,11 +80,17 @@ type DynamicObj() = member this.GetPropertyHelpers (includeInstanceProperties) = - #if FABLE_COMPILER + #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT FableJS.getPropertyHelpers this |> Seq.filter (fun pd -> includeInstanceProperties || pd.IsDynamic ) + #endif + #if FABLE_COMPILER_PYTHON + FablePy.getPropertyHelpers this + |> Seq.filter (fun pd -> + includeInstanceProperties || pd.IsDynamic + ) #else seq [ if includeInstanceProperties then @@ -104,7 +113,7 @@ type DynamicObj() = /// Returns both instance and dynamic properties when passed true, only dynamic properties otherwise. /// Properties are returned as a key value pair of the member names and the boxed values member this.GetProperties includeInstanceProperties : seq> = - #if FABLE_COMPILER + #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT FableJS.getPropertyHelpers this |> Seq.choose (fun pd -> if includeInstanceProperties || pd.IsDynamic then @@ -113,6 +122,16 @@ type DynamicObj() = else None ) + #endif + #if FABLE_COMPILER_PYTHON + FablePy.getPropertyHelpers this + |> Seq.choose (fun pd -> + if includeInstanceProperties || pd.IsDynamic then + new KeyValuePair(pd.Name, pd.GetValue this) + |> Some + else + None + ) #else seq [ if includeInstanceProperties then diff --git a/src/DynamicObj/DynamicObj.fsproj b/src/DynamicObj/DynamicObj.fsproj index 68092f6..6caca99 100644 --- a/src/DynamicObj/DynamicObj.fsproj +++ b/src/DynamicObj/DynamicObj.fsproj @@ -26,6 +26,8 @@ + + diff --git a/src/DynamicObj/FableJS.fs b/src/DynamicObj/FableJS.fs new file mode 100644 index 0000000..b8a5e3e --- /dev/null +++ b/src/DynamicObj/FableJS.fs @@ -0,0 +1,148 @@ +namespace DynamicObj + + +open Fable.Core + +module FableJS = + + module PropertyDescriptor = + + [] + let tryGetPropertyValue (o:obj) (propName:string) : obj option = + jsNative + + let tryGetIsWritable (o:obj) : bool option = + tryGetPropertyValue o "writable" + |> Option.map (fun v -> v :?> bool) + + let containsGetter (o:obj) : bool = + match tryGetPropertyValue o "get" with + | Some _ -> true + | None -> false + + let containsSetter (o:obj) : bool = + match tryGetPropertyValue o "set" with + | Some _ -> true + | None -> false + + let isWritable (o:obj) : bool = + match tryGetIsWritable o with + | Some v -> v + | None -> containsSetter o + + [] + let valueIsFunction (o:obj) : bool = + jsNative + + let isFunction (o:obj) : bool = + match tryGetPropertyValue o "value" with + | Some v -> valueIsFunction v + | None -> false + + [] + let getOwnPropertyNames (o:obj) : string [] = + jsNative + + [] + let getPrototype (o:obj) : obj = + jsNative + + let getStaticPropertyNames (o:obj) = + getPrototype o + |> getOwnPropertyNames + |> Array.filter (fun n -> n <> "constructor") + + [] + let setPropertyValue (o:obj) (propName:string) (value:obj) = + jsNative + + let createSetter (propName:string) = + fun (o:obj) (value:obj) -> + setPropertyValue o propName value + + let removeStaticPropertyValue (o:obj) (propName:string) = + setPropertyValue o propName null + + [] + let deleteDynamicPropertyValue (o:obj) (propName:string) = + jsNative + + let createRemover (propName:string) (isStatic : bool) = + if isStatic then + fun (o:obj) -> + removeStaticPropertyValue o propName + else + fun (o:obj) -> + deleteDynamicPropertyValue o propName + + + [] + let getPropertyValue (o:obj) (propName:string) = + jsNative + + let createGetter (propName:string) = + fun (o:obj) -> + getPropertyValue o propName + + [] + let getPropertyDescriptor (o:obj) (propName:string) = + jsNative + + let getStaticPropertyDescriptor (o:obj) (propName:string) = + getPropertyDescriptor (getPrototype o) propName + + let getStaticPropertyHelpers (o:obj) : PropertyHelper [] = + getStaticPropertyNames o + |> Array.choose (fun n -> + let pd = getStaticPropertyDescriptor o n + if PropertyDescriptor.isFunction pd then + None + else + let isWritable = PropertyDescriptor.isWritable pd + { + Name = n + IsStatic = true + IsDynamic = false + IsMutable = isWritable + IsImmutable = not isWritable + GetValue = createGetter n + SetValue = createSetter n + RemoveValue = createRemover n true + } + |> Some + ) + + let transpiledPropertyRegex = "^[a-zA-Z]+@[0-9]+$" + + let isTranspiledPropertyHelper (propertyName : string) = + System.Text.RegularExpressions.Regex.IsMatch(propertyName, transpiledPropertyRegex) + + let getDynamicPropertyHelpers (o:obj) : PropertyHelper [] = + getOwnPropertyNames o + |> Array.choose (fun n -> + let pd = getPropertyDescriptor o n + if PropertyDescriptor.isFunction pd || isTranspiledPropertyHelper n then + None + else + let isWritable = PropertyDescriptor.isWritable pd + { + Name = n + IsStatic = false + IsDynamic = true + IsMutable = isWritable + IsImmutable = not isWritable + GetValue = createGetter n + SetValue = createSetter n + RemoveValue = createRemover n false + } + |> Some + ) + + let getPropertyHelpers (o:obj) = + getDynamicPropertyHelpers o + |> Array.append (getStaticPropertyHelpers o) + + let getPropertyNames (o:obj) = + getPropertyHelpers o + |> Array.map (fun h -> h.Name) + diff --git a/src/DynamicObj/FablePy.fs b/src/DynamicObj/FablePy.fs new file mode 100644 index 0000000..a19aa1f --- /dev/null +++ b/src/DynamicObj/FablePy.fs @@ -0,0 +1,149 @@ +namespace DynamicObj + + +open Fable.Core + + +module FablePy = + + module PropertyDescriptor = + + [] + let tryGetPropertyValue (o:obj) (propName:string) : obj option = + PyInterop.em + + let tryGetIsWritable (o:obj) : bool option = + tryGetPropertyValue o "writable" + |> Option.map (fun v -> v :?> bool) + + let containsGetter (o:obj) : bool = + match tryGetPropertyValue o "get" with + | Some _ -> true + | None -> false + + let containsSetter (o:obj) : bool = + match tryGetPropertyValue o "set" with + | Some _ -> true + | None -> false + + let isWritable (o:obj) : bool = + match tryGetIsWritable o with + | Some v -> v + | None -> containsSetter o + + [] + let valueIsFunction (o:obj) : bool = + nativeOnly + + let isFunction (o:obj) : bool = + match tryGetPropertyValue o "value" with + | Some v -> valueIsFunction v + | None -> false + + [] + let getOwnPropertyNames (o:obj) : string [] = + nativeOnly + + [] + let getPrototype (o:obj) : obj = + nativeOnly + + let getStaticPropertyNames (o:obj) = + getPrototype o + |> getOwnPropertyNames + |> Array.filter (fun n -> n <> "constructor") + + [] + let setPropertyValue (o:obj) (propName:string) (value:obj) = + nativeOnly + + let createSetter (propName:string) = + fun (o:obj) (value:obj) -> + setPropertyValue o propName value + + let removeStaticPropertyValue (o:obj) (propName:string) = + setPropertyValue o propName null + + [] + let deleteDynamicPropertyValue (o:obj) (propName:string) = + nativeOnly + + let createRemover (propName:string) (isStatic : bool) = + if isStatic then + fun (o:obj) -> + removeStaticPropertyValue o propName + else + fun (o:obj) -> + deleteDynamicPropertyValue o propName + + + [] + let getPropertyValue (o:obj) (propName:string) = + nativeOnly + + let createGetter (propName:string) = + fun (o:obj) -> + getPropertyValue o propName + + [] + let getPropertyDescriptor (o:obj) (propName:string) = + nativeOnly + + let getStaticPropertyDescriptor (o:obj) (propName:string) = + getPropertyDescriptor (getPrototype o) propName + + let getStaticPropertyHelpers (o:obj) : PropertyHelper [] = + getStaticPropertyNames o + |> Array.choose (fun n -> + let pd = getStaticPropertyDescriptor o n + if PropertyDescriptor.isFunction pd then + None + else + let isWritable = PropertyDescriptor.isWritable pd + { + Name = n + IsStatic = true + IsDynamic = false + IsMutable = isWritable + IsImmutable = not isWritable + GetValue = createGetter n + SetValue = createSetter n + RemoveValue = createRemover n true + } + |> Some + ) + + let transpiledPropertyRegex = "^[a-zA-Z]+@[0-9]+$" + + let isTranspiledPropertyHelper (propertyName : string) = + System.Text.RegularExpressions.Regex.IsMatch(propertyName, transpiledPropertyRegex) + + let getDynamicPropertyHelpers (o:obj) : PropertyHelper [] = + getOwnPropertyNames o + |> Array.choose (fun n -> + let pd = getPropertyDescriptor o n + if PropertyDescriptor.isFunction pd || isTranspiledPropertyHelper n then + None + else + let isWritable = PropertyDescriptor.isWritable pd + { + Name = n + IsStatic = false + IsDynamic = true + IsMutable = isWritable + IsImmutable = not isWritable + GetValue = createGetter n + SetValue = createSetter n + RemoveValue = createRemover n false + } + |> Some + ) + + let getPropertyHelpers (o:obj) = + getDynamicPropertyHelpers o + |> Array.append (getStaticPropertyHelpers o) + + let getPropertyNames (o:obj) = + getPropertyHelpers o + |> Array.map (fun h -> h.Name) + diff --git a/src/DynamicObj/ReflectionUtils.fs b/src/DynamicObj/ReflectionUtils.fs index 1857267..fea4084 100644 --- a/src/DynamicObj/ReflectionUtils.fs +++ b/src/DynamicObj/ReflectionUtils.fs @@ -1,151 +1,5 @@ namespace DynamicObj -open Fable.Core - -module FableJS = - - module PropertyDescriptor = - - [] - let tryGetPropertyValue (o:obj) (propName:string) : obj option = - jsNative - - let tryGetIsWritable (o:obj) : bool option = - tryGetPropertyValue o "writable" - |> Option.map (fun v -> v :?> bool) - - let containsGetter (o:obj) : bool = - match tryGetPropertyValue o "get" with - | Some _ -> true - | None -> false - - let containsSetter (o:obj) : bool = - match tryGetPropertyValue o "set" with - | Some _ -> true - | None -> false - - let isWritable (o:obj) : bool = - match tryGetIsWritable o with - | Some v -> v - | None -> containsSetter o - - [] - let valueIsFunction (o:obj) : bool = - jsNative - - let isFunction (o:obj) : bool = - match tryGetPropertyValue o "value" with - | Some v -> valueIsFunction v - | None -> false - - [] - let getOwnPropertyNames (o:obj) : string [] = - jsNative - - [] - let getPrototype (o:obj) : obj = - jsNative - - let getStaticPropertyNames (o:obj) = - getPrototype o - |> getOwnPropertyNames - |> Array.filter (fun n -> n <> "constructor") - - [] - let setPropertyValue (o:obj) (propName:string) (value:obj) = - jsNative - - let createSetter (propName:string) = - fun (o:obj) (value:obj) -> - setPropertyValue o propName value - - let removeStaticPropertyValue (o:obj) (propName:string) = - setPropertyValue o propName null - - [] - let deleteDynamicPropertyValue (o:obj) (propName:string) = - jsNative - - let createRemover (propName:string) (isStatic : bool) = - if isStatic then - fun (o:obj) -> - removeStaticPropertyValue o propName - else - fun (o:obj) -> - deleteDynamicPropertyValue o propName - - - [] - let getPropertyValue (o:obj) (propName:string) = - jsNative - - let createGetter (propName:string) = - fun (o:obj) -> - getPropertyValue o propName - - [] - let getPropertyDescriptor (o:obj) (propName:string) = - jsNative - - let getStaticPropertyDescriptor (o:obj) (propName:string) = - getPropertyDescriptor (getPrototype o) propName - - let getStaticPropertyHelpers (o:obj) : PropertyHelper [] = - getStaticPropertyNames o - |> Array.choose (fun n -> - let pd = getStaticPropertyDescriptor o n - if PropertyDescriptor.isFunction pd then - None - else - let isWritable = PropertyDescriptor.isWritable pd - { - Name = n - IsStatic = true - IsDynamic = false - IsMutable = isWritable - IsImmutable = not isWritable - GetValue = createGetter n - SetValue = createSetter n - RemoveValue = createRemover n true - } - |> Some - ) - - let transpiledPropertyRegex = "^[a-zA-Z]+@[0-9]+$" - - let isTranspiledPropertyHelper (propertyName : string) = - System.Text.RegularExpressions.Regex.IsMatch(propertyName, transpiledPropertyRegex) - - let getDynamicPropertyHelpers (o:obj) : PropertyHelper [] = - getOwnPropertyNames o - |> Array.choose (fun n -> - let pd = getPropertyDescriptor o n - if PropertyDescriptor.isFunction pd || isTranspiledPropertyHelper n then - None - else - let isWritable = PropertyDescriptor.isWritable pd - { - Name = n - IsStatic = false - IsDynamic = true - IsMutable = isWritable - IsImmutable = not isWritable - GetValue = createGetter n - SetValue = createSetter n - RemoveValue = createRemover n false - } - |> Some - ) - - let getPropertyHelpers (o:obj) = - getDynamicPropertyHelpers o - |> Array.append (getStaticPropertyHelpers o) - - let getPropertyNames (o:obj) = - getPropertyHelpers o - |> Array.map (fun h -> h.Name) - - module ReflectionUtils = @@ -156,6 +10,9 @@ module ReflectionUtils = let getStaticProperties (o : obj) = #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT FableJS.getStaticPropertyHelpers o + #endif + #if FABLE_COMPILER_PYTHON + FablePy.getStaticPropertyHelpers o #else let t = o.GetType() [| @@ -169,6 +26,9 @@ module ReflectionUtils = let tryGetPropertyInfo (o:obj) (propName:string) = #if FABLE_COMPILER_JAVASCRIPT || FABLE_COMPILER_TYPESCRIPT FableJS.getPropertyHelpers o + #endif + #if FABLE_COMPILER_PYTHON + FablePy.getPropertyHelpers o #else getStaticProperties (o) #endif From 897c0495a52d3d95c54199a0f648d891e2833eae Mon Sep 17 00:00:00 2001 From: Heinrich Lukas Weil Date: Mon, 19 Aug 2024 10:31:52 +0200 Subject: [PATCH 14/20] changes for fable python compatability --- src/DynamicObj/FablePy.fs | 54 ++++++++++++------------ tests/DynamicObject.Tests/Inheritance.fs | 2 +- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/DynamicObj/FablePy.fs b/src/DynamicObj/FablePy.fs index a19aa1f..180ce25 100644 --- a/src/DynamicObj/FablePy.fs +++ b/src/DynamicObj/FablePy.fs @@ -2,54 +2,56 @@ open Fable.Core - +open System.Collections.Generic module FablePy = - module PropertyDescriptor = + type PropertyObject = + abstract fget : obj + abstract fset : obj + + module PropertyObject = - [] - let tryGetPropertyValue (o:obj) (propName:string) : obj option = - PyInterop.em + [] + let tryGetGetter (o:PropertyObject) : obj option = + nativeOnly - let tryGetIsWritable (o:obj) : bool option = - tryGetPropertyValue o "writable" - |> Option.map (fun v -> v :?> bool) + [] + let tryGetSetter (o:PropertyObject) : obj option = + nativeOnly let containsGetter (o:obj) : bool = - match tryGetPropertyValue o "get" with + match tryGetGetter o with | Some _ -> true | None -> false let containsSetter (o:obj) : bool = - match tryGetPropertyValue o "set" with + match tryGetSetter o with | Some _ -> true | None -> false let isWritable (o:obj) : bool = - match tryGetIsWritable o with - | Some v -> v - | None -> containsSetter o + containsSetter o - [] - let valueIsFunction (o:obj) : bool = + [] + let isProperty (o:obj) : bool = nativeOnly - let isFunction (o:obj) : bool = - match tryGetPropertyValue o "value" with - | Some v -> valueIsFunction v - | None -> false - - [] - let getOwnPropertyNames (o:obj) : string [] = + [] + let getOwnMemberObjects (o:obj) : Dictionary = nativeOnly - [] - let getPrototype (o:obj) : obj = + [] + let getClass (o:obj) : obj = nativeOnly - let getStaticPropertyNames (o:obj) = - getPrototype o + let getOwnPropertyObjects (o:obj) : Dictionary = + getOwnMemberObjects o + + + + let getStaticPropertyObjects (o:obj) = + getClass o |> getOwnPropertyNames |> Array.filter (fun n -> n <> "constructor") diff --git a/tests/DynamicObject.Tests/Inheritance.fs b/tests/DynamicObject.Tests/Inheritance.fs index 215d22e..8b509d4 100644 --- a/tests/DynamicObject.Tests/Inheritance.fs +++ b/tests/DynamicObject.Tests/Inheritance.fs @@ -53,7 +53,7 @@ let tests_set = testList "Set" [ Expect.equal p1 p2 "Values should be equal" Expect.equal (p1.GetHashCode()) (p2.GetHashCode()) "Hash codes should be equal" - ptestCase "Dynamic Property Only on one" <| fun _ -> + testCase "Dynamic Property Only on one" <| fun _ -> let p1 = Person("John") let p2 = Person("John") From 71078db75b9cc0b1cbdf6d5509a0a39e4f032b8d Mon Sep 17 00:00:00 2001 From: HLWeil Date: Tue, 20 Aug 2024 16:36:53 +0200 Subject: [PATCH 15/20] finish up first working version of js and py compatible DynamicObj --- src/DynamicObj/DynObj.fs | 2 +- src/DynamicObj/DynamicObj.fs | 19 ++- src/DynamicObj/FablePy.fs | 192 ++++++++++++++++++++---------- src/DynamicObj/ReflectionUtils.fs | 6 +- 4 files changed, 144 insertions(+), 75 deletions(-) diff --git a/src/DynamicObj/DynObj.fs b/src/DynamicObj/DynObj.fs index 78bcb0f..685e631 100644 --- a/src/DynamicObj/DynObj.fs +++ b/src/DynamicObj/DynObj.fs @@ -82,7 +82,7 @@ module DynObj = dyn.TryGetValue name let remove (dyn:DynamicObj) propName = - DynamicObj.Remove (dyn, propName) |> ignore + DynamicObj.remove (dyn, propName) |> ignore let format (d:DynamicObj) = diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index 74c3d6e..55b2d28 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -38,6 +38,9 @@ type DynamicObj() = // Next check for Public properties via Reflection | _ -> ReflectionUtils.tryGetPropertyValue this name + + member this.GetValue (name) = + this.TryGetValue(name).Value /// Gets property value member this.TryGetTypedValue<'a> name = @@ -65,7 +68,8 @@ type DynamicObj() = #endif #if FABLE_COMPILER_PYTHON FablePy.setPropertyValue this name value - #else + #endif + #if !FABLE_COMPILER // Next check the Properties collection for member match properties.TryGetValue name with | true,_ -> properties.[name] <- value @@ -91,7 +95,8 @@ type DynamicObj() = |> Seq.filter (fun pd -> includeInstanceProperties || pd.IsDynamic ) - #else + #endif + #if !FABLE_COMPILER seq [ if includeInstanceProperties then yield! ReflectionUtils.getStaticProperties (this) @@ -132,7 +137,8 @@ type DynamicObj() = else None ) - #else + #endif + #if !FABLE_COMPILER seq [ if includeInstanceProperties then for prop in ReflectionUtils.getStaticProperties (this) -> @@ -170,10 +176,11 @@ type DynamicObj() = // this.CopyDynamicPropertiesTo(target) // target - static member GetValue (lookup:DynamicObj,name) = - lookup.TryGetValue(name).Value - static member Remove (lookup:DynamicObj,name) = + static member getValue (lookup:DynamicObj,name) = + lookup.GetValue(name) + + static member remove (lookup:DynamicObj,name) = lookup.Remove(name) override this.Equals o = diff --git a/src/DynamicObj/FablePy.fs b/src/DynamicObj/FablePy.fs index 180ce25..0e43cca 100644 --- a/src/DynamicObj/FablePy.fs +++ b/src/DynamicObj/FablePy.fs @@ -6,6 +6,21 @@ open System.Collections.Generic module FablePy = + module Dictionary = + + let ofSeq (s:seq>) = + let d = new System.Collections.Generic.Dictionary<_,_>() + s |> Seq.iter (fun kv -> d.Add(kv.Key, kv.Value)) + d + + let choose (f: 'T -> 'U option) (d:System.Collections.Generic.Dictionary<_,'T>) = + let nd = new System.Collections.Generic.Dictionary<_,'U>() + for kv in d do + match f kv.Value with + | Some v -> nd.Add(kv.Key, v) + | None -> () + nd + type PropertyObject = abstract fget : obj abstract fset : obj @@ -13,60 +28,80 @@ module FablePy = module PropertyObject = [] - let tryGetGetter (o:PropertyObject) : obj option = + let tryGetGetter (o:PropertyObject) : (obj -> obj) option = nativeOnly [] - let tryGetSetter (o:PropertyObject) : obj option = + let tryGetSetter (o:PropertyObject) : (obj -> obj -> unit) option = nativeOnly - let containsGetter (o:obj) : bool = + let getGetter (o : PropertyObject) : obj -> obj = + match tryGetGetter o with + | Some f -> f + | None -> fun o -> failwith ("Property does not contain getter") + + let getSetter (o:PropertyObject) : obj -> obj -> unit = + match tryGetSetter o with + | Some f -> f + | None -> fun s o -> failwith ("Property does not contain setter") + + let containsGetter (o:PropertyObject) : bool = match tryGetGetter o with | Some _ -> true | None -> false - let containsSetter (o:obj) : bool = + let containsSetter (o:PropertyObject) : bool = match tryGetSetter o with | Some _ -> true | None -> false - let isWritable (o:obj) : bool = + let isWritable (o:PropertyObject) : bool = containsSetter o [] let isProperty (o:obj) : bool = nativeOnly - [] - let getOwnMemberObjects (o:obj) : Dictionary = - nativeOnly + let tryProperty (o:obj) : PropertyObject option = + if isProperty o then + Some (o :?> PropertyObject) + else + None - [] - let getClass (o:obj) : obj = + [] + let getPropertyValue (o:obj) (propName:string) = nativeOnly - let getOwnPropertyObjects (o:obj) : Dictionary = - getOwnMemberObjects o - - - - let getStaticPropertyObjects (o:obj) = - getClass o - |> getOwnPropertyNames - |> Array.filter (fun n -> n <> "constructor") + let createGetter (propName:string) = + fun (o:obj) -> + getPropertyValue o propName - [] - let setPropertyValue (o:obj) (propName:string) (value:obj) = + [] + let setPropertyValue (o:obj) (propName:string) (value:obj) : unit = nativeOnly let createSetter (propName:string) = fun (o:obj) (value:obj) -> setPropertyValue o propName value + + [] + let getOwnMemberObjects (o:obj) : Dictionary = + nativeOnly + + [] + let getClass (o:obj) : obj = + nativeOnly + + let getStaticPropertyObjects (o:obj) : Dictionary = + getClass o + |> getOwnMemberObjects + |> Dictionary.choose PropertyObject.tryProperty + let removeStaticPropertyValue (o:obj) (propName:string) = setPropertyValue o propName null - [] + [] let deleteDynamicPropertyValue (o:obj) (propName:string) = nativeOnly @@ -79,67 +114,92 @@ module FablePy = deleteDynamicPropertyValue o propName - [] - let getPropertyValue (o:obj) (propName:string) = - nativeOnly - let createGetter (propName:string) = - fun (o:obj) -> - getPropertyValue o propName - - [] - let getPropertyDescriptor (o:obj) (propName:string) = + [] + let getMemberObject (o:obj) (propName:string) = nativeOnly - let getStaticPropertyDescriptor (o:obj) (propName:string) = - getPropertyDescriptor (getPrototype o) propName - - let getStaticPropertyHelpers (o:obj) : PropertyHelper [] = - getStaticPropertyNames o - |> Array.choose (fun n -> - let pd = getStaticPropertyDescriptor o n - if PropertyDescriptor.isFunction pd then - None - else - let isWritable = PropertyDescriptor.isWritable pd - { - Name = n - IsStatic = true - IsDynamic = false - IsMutable = isWritable - IsImmutable = not isWritable - GetValue = createGetter n - SetValue = createSetter n - RemoveValue = createRemover n true - } - |> Some - ) - - let transpiledPropertyRegex = "^[a-zA-Z]+@[0-9]+$" + let tryGetPropertyObject (o:obj) (propName:string) : PropertyObject option = + match PropertyObject.tryProperty (getMemberObject o propName) with + | Some po -> Some po + | None -> None + + let tryGetDynamicPropertyHelper (o:obj) (propName:string) : PropertyHelper option = + match getMemberObject o propName with + | Some _ -> + Some { + Name = propName + IsStatic = false + IsDynamic = true + IsMutable = true + IsImmutable = false + GetValue = createGetter propName + SetValue = createSetter propName + RemoveValue = fun o -> deleteDynamicPropertyValue o propName + } + | None -> None + + let tryGetStaticPropertyHelper (o:obj) (propName:string) : PropertyHelper option = + match tryGetPropertyObject (getClass o) propName with + | Some po -> + let isWritable = PropertyObject.isWritable po + Some { + Name = propName + IsStatic = true + IsDynamic = false + IsMutable = isWritable + IsImmutable = not isWritable + GetValue = createGetter propName + SetValue = createSetter propName + RemoveValue = fun o -> removeStaticPropertyValue o propName + } + | None -> None + + let transpiledPropertyRegex = "^[a-zA-Z]+_[0-9]+$" let isTranspiledPropertyHelper (propertyName : string) = System.Text.RegularExpressions.Regex.IsMatch(propertyName, transpiledPropertyRegex) + let getDynamicPropertyHelpers (o:obj) : PropertyHelper [] = - getOwnPropertyNames o - |> Array.choose (fun n -> - let pd = getPropertyDescriptor o n - if PropertyDescriptor.isFunction pd || isTranspiledPropertyHelper n then + getOwnMemberObjects o + |> Seq.choose (fun kv -> + let n = kv.Key + if isTranspiledPropertyHelper n then None - else - let isWritable = PropertyDescriptor.isWritable pd + else { Name = n IsStatic = false IsDynamic = true - IsMutable = isWritable - IsImmutable = not isWritable + IsMutable = true + IsImmutable = false GetValue = createGetter n SetValue = createSetter n - RemoveValue = createRemover n false - } + RemoveValue = fun o -> deleteDynamicPropertyValue o n + } |> Some ) + |> Seq.toArray + + + let getStaticPropertyHelpers (o:obj) : PropertyHelper [] = + getStaticPropertyObjects o + |> Seq.map (fun kv -> + let n = kv.Key + let po = kv.Value + { + Name = n + IsStatic = true + IsDynamic = false + IsMutable = PropertyObject.isWritable po + IsImmutable = not (PropertyObject.isWritable po) + GetValue = createGetter n + SetValue = createSetter n + RemoveValue = fun o -> removeStaticPropertyValue o n + } + ) + |> Seq.toArray let getPropertyHelpers (o:obj) = getDynamicPropertyHelpers o diff --git a/src/DynamicObj/ReflectionUtils.fs b/src/DynamicObj/ReflectionUtils.fs index fea4084..75d6452 100644 --- a/src/DynamicObj/ReflectionUtils.fs +++ b/src/DynamicObj/ReflectionUtils.fs @@ -13,7 +13,8 @@ module ReflectionUtils = #endif #if FABLE_COMPILER_PYTHON FablePy.getStaticPropertyHelpers o - #else + #endif + #if !FABLE_COMPILER let t = o.GetType() [| for propInfo in t.GetProperties() -> propInfo @@ -29,7 +30,8 @@ module ReflectionUtils = #endif #if FABLE_COMPILER_PYTHON FablePy.getPropertyHelpers o - #else + #endif + #if !FABLE_COMPILER getStaticProperties (o) #endif |> Array.tryFind (fun n -> n.Name = propName) From b676a1764f0063549aa06c246b218fa25be9072d Mon Sep 17 00:00:00 2001 From: HLWeil Date: Tue, 20 Aug 2024 16:57:28 +0200 Subject: [PATCH 16/20] small fixes --- pyproject.toml | 8 ++++---- src/DynamicObj/DynamicObj.fs | 14 +------------- src/DynamicObj/ReflectionUtils.fs | 2 +- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 33693a1..0fad45c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,11 +2,11 @@ name = "DynamicObj" version = "3.0.0" description = "Fable compatible library supporting Dynamic Objects including inheritance in functional style." -authors = ["Heinrich Lukas Weil ", "Kevin Frey "] -maintainers = ["Florian Wetzels"] +authors = [""] +maintainers = [""] readme = "README.md" -repository = "https://github.com/nfdi4plants/ARCtrl" -keywords = ["arc", "annotated research context", "isa", "research data", "multi platform"] +repository = "" +keywords = [""] [tool.poetry.dependencies] python = "^3.10" diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index 55b2d28..80bf509 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -4,21 +4,9 @@ open System.Collections.Generic open Fable.Core -module Fable = - - [] - let getProperty (o:obj) (propName:string) : 'a = - jsNative - - [] - let getPropertyNames (o:obj) : string seq = - jsNative - [] type DynamicObj() = - - let mutable properties = new Dictionary() member this.Properties @@ -77,7 +65,7 @@ type DynamicObj() = #endif member this.Remove name = - match ReflectionUtils.removeStaticProperty this name with + match ReflectionUtils.removeProperty this name with | true -> true // Maybe in map | false -> properties.Remove(name) diff --git a/src/DynamicObj/ReflectionUtils.fs b/src/DynamicObj/ReflectionUtils.fs index 75d6452..02f4b0c 100644 --- a/src/DynamicObj/ReflectionUtils.fs +++ b/src/DynamicObj/ReflectionUtils.fs @@ -63,7 +63,7 @@ module ReflectionUtils = | :? System.Reflection.TargetInvocationException -> None | :? System.NullReferenceException -> None - let removeStaticProperty (o:obj) (propName:string) = + let removeProperty (o:obj) (propName:string) = match tryGetPropertyInfo o propName with | Some property when property.IsMutable -> From 5446a590f6cf8b705ad0689a8019730867939570 Mon Sep 17 00:00:00 2001 From: HLWeil Date: Tue, 20 Aug 2024 17:01:10 +0200 Subject: [PATCH 17/20] fix github build workflow for Fable --- .github/workflows/build-and-test.yml | 70 ++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 2a04338..a6fab49 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -6,34 +6,64 @@ on: pull_request: branches: [ main ] - jobs: - build-and-test-linux: + test: + + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + + # SETUP .NET - name: Setup .NET - uses: actions/setup-dotnet@v1 + uses: actions/setup-dotnet@v3 with: dotnet-version: 6.x.x - - name: make script executable - run: chmod u+x build.sh - - name: Build and test - working-directory: ./ - run: ./build.sh runtests + - name: Restore fable + run: dotnet tool restore - build-and-test-windows: - - runs-on: windows-latest + # SETUP NODE + - name: Setup Node.js environment + uses: actions/setup-node@v3 + with: + node-version: 16 + - name: install node modules + working-directory: ./ + run: npm install --ignore-scripts - steps: - - uses: actions/checkout@v2 - - name: Setup .NET - uses: actions/setup-dotnet@v1 + # SETUP PYTHON + - name: Setup Python + uses: actions/setup-python@v5 with: - dotnet-version: 6.x.x - - name: Build and test + python-version: '3.11' + - name: Setup Virtual Environment + run: python -m venv .venv + - name: Setup Poetry Windows + if: matrix.os == 'windows-latest' + run: | + .\.venv\Scripts\python.exe -m pip install -U pip setuptools + .\.venv\Scripts\python.exe -m pip install poetry + .\.venv\Scripts\python.exe -m poetry install --no-root + - name: Setup Poetry Unix + if: matrix.os == 'ubuntu-latest' + run: | + ./.venv/bin/python -m pip install -U pip setuptools + ./.venv/bin/python -m pip install poetry + ./.venv/bin/python -m poetry install --no-root + + # BUILD + - name: make script executable + if: matrix.os == 'ubuntu-latest' + run: chmod u+x build.sh + - name: Test (Unix) + if: matrix.os == 'ubuntu-latest' working-directory: ./ - run: ./build.cmd runtests \ No newline at end of file + run: ./build.sh runtests + - name: Test (Windows) + if: matrix.os == 'windows-latest' + run: .\build.cmd runtests \ No newline at end of file From 7928cedcbf9a970923fee5b045e8ed0aee6e32fb Mon Sep 17 00:00:00 2001 From: HLWeil Date: Wed, 21 Aug 2024 10:48:37 +0200 Subject: [PATCH 18/20] cleanup and mark non-fable compatible code --- src/DynamicObj/DynamicObj.fs | 7 +++++ src/DynamicObj/ReflectionUtils.fs | 47 ------------------------------- 2 files changed, 7 insertions(+), 47 deletions(-) diff --git a/src/DynamicObj/DynamicObj.fs b/src/DynamicObj/DynamicObj.fs index 80bf509..8f6ac2f 100644 --- a/src/DynamicObj/DynamicObj.fs +++ b/src/DynamicObj/DynamicObj.fs @@ -141,13 +141,20 @@ type DynamicObj() = this.GetProperties(includeInstanceProperties) |> Seq.map (fun kv -> kv.Key) + + /// /// Operator to access a dynamic member by name + /// + /// This operator is not Fable-compatible static member (?) (lookup:#DynamicObj,name:string) = match lookup.TryGetValue name with | Some(value) -> value | None -> raise <| System.MemberAccessException() + /// /// Operator to set a dynamic member + /// + /// This operator is not Fable-compatible static member (?<-) (lookup:#DynamicObj,name:string,value:'v) = lookup.SetValue (name,value) diff --git a/src/DynamicObj/ReflectionUtils.fs b/src/DynamicObj/ReflectionUtils.fs index 02f4b0c..fdddbaa 100644 --- a/src/DynamicObj/ReflectionUtils.fs +++ b/src/DynamicObj/ReflectionUtils.fs @@ -70,50 +70,3 @@ module ReflectionUtils = property.RemoveValue(o) true | _ -> false - - - - - #if !FABLE_COMPILER - /// Creates an instance of the Object according to applyStyle and applies the function.. - let buildApply (applyStyle:'a -> 'a) = - let instance = - System.Activator.CreateInstance<'a>() - applyStyle instance - - /// Applies 'applyStyle' to item option. If None it creates a new instance. - let optBuildApply (applyStyle:'a -> 'a) (item:'a option) = - match item with - | Some item' -> applyStyle item' - | None -> buildApply applyStyle - - /// Applies Some 'applyStyle' to item. If None it returns 'item' unchanged. - let optApply (applyStyle:('a -> 'a) option) (item:'a ) = - match applyStyle with - | Some apply -> apply item - | None -> item - - /// Returns the proptery name from quotation expression - let tryGetPropertyName (expr : Microsoft.FSharp.Quotations.Expr) = - match expr with - | Microsoft.FSharp.Quotations.Patterns.PropertyGet (_,pInfo,_) -> Some pInfo.Name - | _ -> None - - /// Updates property value by given function - let tryUpdatePropertyValueFromName (o:obj) (propName:string) (f: 'a -> 'a) = - let v = optBuildApply f (tryGetPropertyValueAs<'a> o propName) - trySetPropertyValue o propName v - //o - - /// Updates property value by given function - let tryUpdatePropertyValue (o:obj) (expr : Microsoft.FSharp.Quotations.Expr) (f: 'a -> 'a) = - let propName = tryGetPropertyName expr - let g = (tryGetPropertyValueAs<'a> o propName.Value) - let v = optBuildApply f g - trySetPropertyValue o propName.Value v - //o - - let updatePropertyValueAndIgnore (o:obj) (expr : Microsoft.FSharp.Quotations.Expr) (f: 'a -> 'a) = - tryUpdatePropertyValue o expr f |> ignore - - #endif \ No newline at end of file From 1affedb3c8cbf4e804b4f25ad709eb0bae7d3bcb Mon Sep 17 00:00:00 2001 From: HLWeil Date: Wed, 21 Aug 2024 13:02:08 +0200 Subject: [PATCH 19/20] update readme and project files --- README.md | 111 ++++++++++++++++++++++++++++++- pyproject.toml | 8 +-- src/DynamicObj/DynamicObj.fsproj | 5 +- 3 files changed, 118 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 03d2797..90c70f1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,112 @@ # DynamicObj F# library supporting Dynamic Objects including inheritance in functional style. -It builds on ´System.Dynamic´ but adds object inheritance. + +The library is compatible with [Fable](https://github.com/fable-compiler/Fable), allowing transpilation to `javascript` and `python`. + + + +## Usage example + +### Get started + +```fsharp +#r "nuget: DynamicObj" +#r "nuget: Fable.Core" // Needed if working with Fable + +open DynamicObj +open Fable.Core // Needed if working with Fable + +[] // AttachMembers needed if working with Fable +type Person(id : int, name : string) = + + // Include this in your class + inherit DynamicObj() + + let mutable name = name + + // Mutable property + member this.Name + with get() = name + and set(value) = name <- value + + // Immutable property + member this.ID + with get() = id + +let p = Person(1337,"John") +``` + +### Accessing static and dynamic properties + +```fsharp + +// Access Static Properties +p.GetValue("Name") // val it: obj = "John" +p.GetValue("ID") // val it: obj = 1337 + + +// Overwrite mutable static property +p.SetValue("Name","Jane") // val it: unit = () +// Overwrite immutable static property +p.SetValue("ID",1234) // System.Exception: Cannot set value for static, immutable property "ID" +// Set dynamic property +p.SetValue("Address","FunStreet") // val it: unit = () + + +// Access Properties +p.GetValue("Name") // val it: obj = "Jane" +p.Name // val it: string = "Jane" +p.GetValue("ID") // val it: obj = 1337 +p.ID // val it: int = 1337 +p.GetValue("Address") // val it: obj = "FunStreet" +``` + +### Practical helpers + +```fsharp +DynObj.format p +|> printfn "%s" +``` +-> +``` +Name: Jane +ID: 1337 +?Address: FunStreet +``` + +## Development + +#### Requirements + +- [nodejs and npm](https://nodejs.org/en/download) + - verify with `node --version` (Tested with v18.16.1) + - verify with `npm --version` (Tested with v9.2.0) +- [.NET SDK](https://dotnet.microsoft.com/en-us/download) + - verify with `dotnet --version` (Tested with 7.0.306) +- [Python](https://www.python.org/downloads/) + - verify with `py --version` (Tested with 3.12.2, known to work only for >=3.11) + +#### Local Setup + +On windows you can use the `setup.cmd` to run the following steps automatically! + +1. Setup dotnet tools + + `dotnet tool restore` + + +2. Install NPM dependencies + + `npm install` + +3. Setup python environment + + `py -m venv .venv` + +4. Install [Poetry](https://python-poetry.org/) and dependencies + + 1. `.\.venv\Scripts\python.exe -m pip install -U pip setuptools` + 2. `.\.venv\Scripts\python.exe -m pip install poetry` + 3. `.\.venv\Scripts\python.exe -m poetry install --no-root` + +Verify correct setup with `./build.cmd runtests` ✨ \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 0fad45c..9da229c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,11 +2,11 @@ name = "DynamicObj" version = "3.0.0" description = "Fable compatible library supporting Dynamic Objects including inheritance in functional style." -authors = [""] -maintainers = [""] +authors = ["Kevin Schneider", "WhiteBlackGoose", "Heinrich Lukas Weil", "Timo Muehlhaus", "Kevin Frey", "David Zimmer"] +maintainers = ["Heinrich Lukas Weil", "Timo Muehlhaus", "Kevin Schneider"] readme = "README.md" -repository = "" -keywords = [""] +repository = "https://github.com/CSBiology/DynamicObj" +keywords = ["Dynamic Object", "Fable", "FSharp", "Javascript", "Python"] [tool.poetry.dependencies] python = "^3.10" diff --git a/src/DynamicObj/DynamicObj.fsproj b/src/DynamicObj/DynamicObj.fsproj index 6caca99..f58a274 100644 --- a/src/DynamicObj/DynamicObj.fsproj +++ b/src/DynamicObj/DynamicObj.fsproj @@ -12,7 +12,7 @@ - Timo Mühlhaus, Kevin Schneider, F# open source contributors + Timo Mühlhaus, Kevin Schneider, Heinrich Lukas Weil, F# open source contributors F# library supporting Dynamic Objects including inheritance in functional style. MIT https://csbiology.github.io/DynamicObj/ @@ -39,5 +39,8 @@ + + + From 956d551d24628691610109daeaceb7495ff2fe89 Mon Sep 17 00:00:00 2001 From: HLWeil Date: Wed, 21 Aug 2024 13:15:38 +0200 Subject: [PATCH 20/20] update python version in ci --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index a6fab49..9fdbb19 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -40,7 +40,7 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 with: - python-version: '3.11' + python-version: '3.12' - name: Setup Virtual Environment run: python -m venv .venv - name: Setup Poetry Windows