mirror of
https://github.com/apple/pkl.git
synced 2026-06-25 14:56:19 +02:00
Change loading of external readers (#1394)
This introduces breaking changes for external readers are loaded: 1. In PklProject, relative paths are resolved relative to the enclosing PklProject file (make behavior consistent with how other settings work) 2. Make CLI flags blow away any settings set on a PklProject 3. Introduce a new `workingDir` property, which defaults to the PklProject dir The overall goal is to make this behavior consistent with how other settings work. For example, relative paths for other evaluator settings are already relative to the project directory. Additionally, in every other case, CLI flags will overwrite any setting set within PklProject.
This commit is contained in:
+130
@@ -0,0 +1,130 @@
|
||||
amends "../snippetTest.pkl"
|
||||
|
||||
import "pkl:EvaluatorSettings"
|
||||
import "pkl:platform"
|
||||
|
||||
// macOS and linux have the same impl; both POSIX systems
|
||||
local macOS = new platform.OperatingSystem { name = "macOS" }
|
||||
|
||||
local function resolve(base: String, path: String) =
|
||||
new EvaluatorSettings { rootDir = path }.resolveForOs(base, macOS).rootDir
|
||||
|
||||
examples {
|
||||
["resolve()"] {
|
||||
local settings: EvaluatorSettings = new {
|
||||
modulePath {
|
||||
"first/module/path"
|
||||
"second/module/path"
|
||||
}
|
||||
moduleCacheDir = "my/cache/dir"
|
||||
rootDir = "my/root/dir"
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
executable = "path/to/my/executable"
|
||||
}
|
||||
}
|
||||
externalResourceReaders {
|
||||
["foo"] {
|
||||
executable = "path/to/my/executable"
|
||||
}
|
||||
}
|
||||
}
|
||||
settings.resolveForOs("file:///path/to/dir/PklProject", macOS)
|
||||
}
|
||||
|
||||
["relative path"] {
|
||||
resolve("file:///path/to/dir/PklProject", "foo/bar")
|
||||
}
|
||||
|
||||
["absolute path"] {
|
||||
resolve("file:///path/to/dir/PklProject", "/foo/bar")
|
||||
}
|
||||
|
||||
["enclosing URI has spaces"] {
|
||||
resolve("file:///path/to/dir%20with%20spaces/PklProject", "foo/bar")
|
||||
}
|
||||
|
||||
["relative path with dot segments"] {
|
||||
resolve("file:///path/to/dir/PklProject", "../my/module/path")
|
||||
}
|
||||
|
||||
["relative path with dot segments 2"] {
|
||||
resolve("file:///path/to/dir/PklProject", "../../my/module/path")
|
||||
}
|
||||
|
||||
["relative path with dot segments 3"] {
|
||||
resolve("file:///path/to/dir/PklProject", ".")
|
||||
}
|
||||
|
||||
["relative path with dot segments 4"] {
|
||||
resolve("file:///path/to/dir/PklProject", "./")
|
||||
}
|
||||
|
||||
["relative path with dot segments 5"] {
|
||||
resolve("file:///path/to/dir/PklProject", "../../../../../../../../")
|
||||
}
|
||||
|
||||
["file:/ instead of file:///"] {
|
||||
resolve("file:/path/to/dir/PklProject", "foo/bar")
|
||||
}
|
||||
|
||||
["executable with simple name is not resolved"] {
|
||||
local settings: EvaluatorSettings = new {
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
executable = "my-reader"
|
||||
}
|
||||
}
|
||||
}
|
||||
settings.resolveForOs("file:///path/to/dir/PklProject", macOS).externalModuleReaders!!["foo"]
|
||||
.executable
|
||||
}
|
||||
|
||||
["executable with path segments is resolved against enclosingUri"] {
|
||||
local settings: EvaluatorSettings = new {
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
executable = "path/to/reader"
|
||||
}
|
||||
}
|
||||
}
|
||||
settings.resolveForOs("file:///path/to/dir/PklProject", macOS).externalModuleReaders!!["foo"]
|
||||
.executable
|
||||
}
|
||||
|
||||
["workingDir defaults to enclosingUri"] {
|
||||
local settings: EvaluatorSettings = new {
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
workingDir = null
|
||||
}
|
||||
}
|
||||
}
|
||||
settings.resolveForOs("file:///path/to/dir/PklProject", macOS).externalModuleReaders!!["foo"]
|
||||
.workingDir
|
||||
}
|
||||
|
||||
["workingDir with relative path"] {
|
||||
local settings: EvaluatorSettings = new {
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
workingDir = "."
|
||||
}
|
||||
}
|
||||
}
|
||||
settings.resolveForOs("file:///path/to/dir/PklProject", macOS).externalModuleReaders!!["foo"]
|
||||
.workingDir
|
||||
}
|
||||
|
||||
["workingDir with absolute path"] {
|
||||
local settings: EvaluatorSettings = new {
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
workingDir = "/foo/bar"
|
||||
}
|
||||
}
|
||||
}
|
||||
settings.resolveForOs("file:///path/to/dir/PklProject", macOS).externalModuleReaders!!["foo"]
|
||||
.workingDir
|
||||
}
|
||||
}
|
||||
+243
@@ -0,0 +1,243 @@
|
||||
amends "../snippetTest.pkl"
|
||||
|
||||
import "pkl:EvaluatorSettings"
|
||||
import "pkl:platform"
|
||||
|
||||
local windows = new platform.OperatingSystem { name = "Windows" }
|
||||
|
||||
local function resolve(base: String, path: String) =
|
||||
new EvaluatorSettings { rootDir = path }
|
||||
.resolveForOs(base, windows)
|
||||
.rootDir
|
||||
|
||||
examples {
|
||||
["resolve()"] {
|
||||
local settings: EvaluatorSettings = new {
|
||||
modulePath {
|
||||
"first/module/path"
|
||||
"second/module/path"
|
||||
}
|
||||
moduleCacheDir = "my/cache/dir"
|
||||
rootDir = "my/root/dir"
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
executable = "path/to/my/executable"
|
||||
}
|
||||
}
|
||||
externalResourceReaders {
|
||||
["foo"] {
|
||||
executable = "path/to/my/executable"
|
||||
}
|
||||
}
|
||||
}
|
||||
settings.resolveForOs("file:///C:/path/to/dir/PklProject", windows)
|
||||
}
|
||||
|
||||
["relative path"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", "foo\\bar")
|
||||
}
|
||||
|
||||
["relative path 2"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", "foo/bar")
|
||||
}
|
||||
|
||||
["absolute path"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", #"\foo\bar"#)
|
||||
}
|
||||
|
||||
["absolute path with drive letter"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", #"C:\foo\bar"#)
|
||||
}
|
||||
|
||||
["absolute path with drive letter 2"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", #"C:/foo/bar"#)
|
||||
}
|
||||
|
||||
["absolute path with drive letter 3"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", #"C:/"#)
|
||||
}
|
||||
|
||||
["absolute path with drive letter 4"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", #"C:"#)
|
||||
}
|
||||
|
||||
["absolute path with drive letter 5"] {
|
||||
resolve("file:///C:", #"\path\to\foo"#)
|
||||
}
|
||||
|
||||
["base path with drive letter"] {
|
||||
resolve("file:///C:", #"path\to\foo"#)
|
||||
}
|
||||
|
||||
["enclosing URI has spaces"] {
|
||||
resolve("file:///C:/path/to/dir%20with%20spaces/PklProject", "foo/bar")
|
||||
}
|
||||
|
||||
["relative path with dot segments"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", "../my/module/path")
|
||||
}
|
||||
|
||||
["relative path with dot segments 2"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", "../../my/module/path")
|
||||
}
|
||||
|
||||
["relative path with dot segments 3"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", ".")
|
||||
}
|
||||
|
||||
["relative path with dot segments 4"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", "./")
|
||||
}
|
||||
|
||||
["relative path with dot segments 5"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", #"..\..\..\..\..\..\..\..\"#)
|
||||
}
|
||||
|
||||
["file URI with no drive"] {
|
||||
resolve("file:///path/to/dir/PklProject", "foo\\bar")
|
||||
}
|
||||
|
||||
["executable with simple name is not resolved"] {
|
||||
local settings: EvaluatorSettings = new {
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
executable = "my-reader"
|
||||
}
|
||||
}
|
||||
}
|
||||
settings
|
||||
.resolveForOs("file:///C:/path/to/dir/PklProject", windows)
|
||||
.externalModuleReaders!!["foo"].executable
|
||||
}
|
||||
|
||||
["executable with path segments is resolved against enclosingUri"] {
|
||||
local settings: EvaluatorSettings = new {
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
executable = "path/to/reader"
|
||||
}
|
||||
}
|
||||
}
|
||||
settings
|
||||
.resolveForOs("file:///C:/path/to/dir/PklProject", windows)
|
||||
.externalModuleReaders!!["foo"].executable
|
||||
}
|
||||
|
||||
["workingDir defaults to enclosingUri"] {
|
||||
local settings: EvaluatorSettings = new {
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
workingDir = null
|
||||
}
|
||||
}
|
||||
}
|
||||
settings
|
||||
.resolveForOs("file:///C:/path/to/dir/PklProject", windows)
|
||||
.externalModuleReaders!!["foo"].workingDir
|
||||
}
|
||||
|
||||
["workingDir with relative path"] {
|
||||
local settings: EvaluatorSettings = new {
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
workingDir = "."
|
||||
}
|
||||
}
|
||||
}
|
||||
settings
|
||||
.resolveForOs("file:///C:/path/to/dir/PklProject", windows)
|
||||
.externalModuleReaders!!["foo"].workingDir
|
||||
}
|
||||
|
||||
["workingDir with absolute path"] {
|
||||
local settings: EvaluatorSettings = new {
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
workingDir = "/foo/bar"
|
||||
}
|
||||
}
|
||||
}
|
||||
settings
|
||||
.resolveForOs("file:///C:/path/to/dir/PklProject", windows)
|
||||
.externalModuleReaders!!["foo"].workingDir
|
||||
}
|
||||
|
||||
["UNC path"] {
|
||||
resolve("file:///path/to/foo", #"\\server\share\path\to\foo"#)
|
||||
}
|
||||
|
||||
["UNC path 2"] {
|
||||
resolve(#"file://server/share/path/to/foo"#, #"\new\path"#)
|
||||
}
|
||||
|
||||
["empty path"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", "")
|
||||
}
|
||||
|
||||
["multiple consecutive slashes"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", #"my\\\\path"#)
|
||||
}
|
||||
|
||||
["different drive letter"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", #"D:\my\path"#)
|
||||
}
|
||||
|
||||
["UNC path as base with relative path"] {
|
||||
resolve(#"file://server/share/dir/PklProject"#, #"my\path"#)
|
||||
}
|
||||
|
||||
["UNC path as base with .."] {
|
||||
resolve(#"file://server/share/dir/sub/PklProject"#, #"..\my\path"#)
|
||||
}
|
||||
|
||||
["UNC path as base with ."] {
|
||||
resolve(#"file://server/share/dir/PklProject"#, ".")
|
||||
}
|
||||
|
||||
["UNC path as base, cannot remove root"] {
|
||||
resolve(#"file://server/share/PklProject"#, #"..\..\..\"#)
|
||||
}
|
||||
|
||||
["UNC path as base, cannot remove root 2"] {
|
||||
resolve(#"file://server/share/dir/PklProject"#, #"../../.."#)
|
||||
}
|
||||
|
||||
["UNC path as base with drive letter path"] {
|
||||
resolve(#"file://server/share/dir/PklProject"#, #"C:\local\path"#)
|
||||
}
|
||||
|
||||
["UNC path as base with no drive"] {
|
||||
resolve(#"file://server/"#, #"/my/path"#)
|
||||
}
|
||||
|
||||
["trailing separator preserved"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", #"my\path\"#)
|
||||
}
|
||||
|
||||
["trailing separator preserved with normalization"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", #"..\my\path\"#)
|
||||
}
|
||||
|
||||
["no trailing separator"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", #"my\path"#)
|
||||
}
|
||||
|
||||
["complex normalization"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", #".\foo\..\bar\.\baz"#)
|
||||
}
|
||||
|
||||
["UNC with forward slashes"] {
|
||||
resolve(#"file://server/share/dir/PklProject"#, "my/path")
|
||||
}
|
||||
|
||||
["absolute path with different drive and forward slashes"] {
|
||||
resolve("file:///C:/path/to/dir/PklProject", "D:/my/path")
|
||||
}
|
||||
}
|
||||
|
||||
output {
|
||||
renderer = new PcfRenderer {
|
||||
useCustomStringDelimiters = true
|
||||
omitNullProperties = true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
amends "../snippetTest.pkl"
|
||||
|
||||
import "pkl:platform"
|
||||
import "pkl:Project"
|
||||
|
||||
examples {
|
||||
local macos = new platform.OperatingSystem { name = "macOS" }
|
||||
|
||||
["resolvedEvaluatorSettings"] {
|
||||
new Project {
|
||||
projectFileUri = "file:///path/to/PklProject"
|
||||
evaluatorSettings {
|
||||
modulePath {
|
||||
"modulepath/first"
|
||||
"modulepath/second"
|
||||
}
|
||||
moduleCacheDir = "path/to/cache"
|
||||
externalModuleReaders {
|
||||
["relative path"] {
|
||||
executable = "foo/executable"
|
||||
}
|
||||
["absolute path"] {
|
||||
executable = "/path/to/executable"
|
||||
}
|
||||
["command name"] {
|
||||
executable = "foo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}.evaluatorSettings.resolveForOs("file:///path/to/PklProject", macos)
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
amends "pkl:Project"
|
||||
|
||||
projectFileUri = "file:///not a valid uri"
|
||||
|
||||
evaluatorSettings {
|
||||
moduleCacheDir = "foo"
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
amends "pkl:Project"
|
||||
|
||||
projectFileUri = "modulepath:/foo/bar/PklProject"
|
||||
|
||||
evaluatorSettings {
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
executable = "foo/bar"
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
amends "pkl:Project"
|
||||
|
||||
evaluatorSettings {
|
||||
rootDir = "."
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
res = read("file:///file.txt")
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
res = read("badRead.pkl").text
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
examples {
|
||||
["resolve()"] {
|
||||
new {
|
||||
modulePath {
|
||||
"/path/to/dir/first/module/path"
|
||||
"/path/to/dir/second/module/path"
|
||||
}
|
||||
moduleCacheDir = "/path/to/dir/my/cache/dir"
|
||||
rootDir = "/path/to/dir/my/root/dir"
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
executable = "/path/to/dir/path/to/my/executable"
|
||||
workingDir = "/path/to/dir"
|
||||
}
|
||||
}
|
||||
externalResourceReaders {
|
||||
["foo"] {
|
||||
executable = "/path/to/dir/path/to/my/executable"
|
||||
workingDir = "/path/to/dir"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
["relative path"] {
|
||||
"/path/to/dir/foo/bar"
|
||||
}
|
||||
["absolute path"] {
|
||||
"/foo/bar"
|
||||
}
|
||||
["enclosing URI has spaces"] {
|
||||
"/path/to/dir with spaces/foo/bar"
|
||||
}
|
||||
["relative path with dot segments"] {
|
||||
"/path/to/my/module/path"
|
||||
}
|
||||
["relative path with dot segments 2"] {
|
||||
"/path/my/module/path"
|
||||
}
|
||||
["relative path with dot segments 3"] {
|
||||
"/path/to/dir"
|
||||
}
|
||||
["relative path with dot segments 4"] {
|
||||
"/path/to/dir"
|
||||
}
|
||||
["relative path with dot segments 5"] {
|
||||
"/"
|
||||
}
|
||||
["file:/ instead of file:///"] {
|
||||
"/path/to/dir/foo/bar"
|
||||
}
|
||||
["executable with simple name is not resolved"] {
|
||||
"my-reader"
|
||||
}
|
||||
["executable with path segments is resolved against enclosingUri"] {
|
||||
"/path/to/dir/path/to/reader"
|
||||
}
|
||||
["workingDir defaults to enclosingUri"] {
|
||||
"/path/to/dir"
|
||||
}
|
||||
["workingDir with relative path"] {
|
||||
"/path/to/dir"
|
||||
}
|
||||
["workingDir with absolute path"] {
|
||||
"/foo/bar"
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
examples {
|
||||
["resolve()"] {
|
||||
new {
|
||||
modulePath {
|
||||
#"C:\path\to\dir\first\module\path"#
|
||||
#"C:\path\to\dir\second\module\path"#
|
||||
}
|
||||
moduleCacheDir = #"C:\path\to\dir\my\cache\dir"#
|
||||
rootDir = #"C:\path\to\dir\my\root\dir"#
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
executable = #"C:\path\to\dir\path\to\my\executable"#
|
||||
workingDir = #"C:\path\to\dir"#
|
||||
}
|
||||
}
|
||||
externalResourceReaders {
|
||||
["foo"] {
|
||||
executable = #"C:\path\to\dir\path\to\my\executable"#
|
||||
workingDir = #"C:\path\to\dir"#
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
["relative path"] {
|
||||
#"C:\path\to\dir\foo\bar"#
|
||||
}
|
||||
["relative path 2"] {
|
||||
#"C:\path\to\dir\foo\bar"#
|
||||
}
|
||||
["absolute path"] {
|
||||
#"C:\foo\bar"#
|
||||
}
|
||||
["absolute path with drive letter"] {
|
||||
#"C:\foo\bar"#
|
||||
}
|
||||
["absolute path with drive letter 2"] {
|
||||
#"C:\foo\bar"#
|
||||
}
|
||||
["absolute path with drive letter 3"] {
|
||||
#"C:\"#
|
||||
}
|
||||
["absolute path with drive letter 4"] {
|
||||
"C:"
|
||||
}
|
||||
["absolute path with drive letter 5"] {
|
||||
#"\\path\to\foo"#
|
||||
}
|
||||
["base path with drive letter"] {
|
||||
#"\path\to\foo"#
|
||||
}
|
||||
["enclosing URI has spaces"] {
|
||||
#"C:\path\to\dir with spaces\foo\bar"#
|
||||
}
|
||||
["relative path with dot segments"] {
|
||||
#"C:\path\to\my\module\path"#
|
||||
}
|
||||
["relative path with dot segments 2"] {
|
||||
#"C:\path\my\module\path"#
|
||||
}
|
||||
["relative path with dot segments 3"] {
|
||||
#"C:\path\to\dir"#
|
||||
}
|
||||
["relative path with dot segments 4"] {
|
||||
#"C:\path\to\dir"#
|
||||
}
|
||||
["relative path with dot segments 5"] {
|
||||
#"C:\"#
|
||||
}
|
||||
["file URI with no drive"] {
|
||||
#"\path\to\dir\foo\bar"#
|
||||
}
|
||||
["executable with simple name is not resolved"] {
|
||||
"my-reader"
|
||||
}
|
||||
["executable with path segments is resolved against enclosingUri"] {
|
||||
#"C:\path\to\dir\path\to\reader"#
|
||||
}
|
||||
["workingDir defaults to enclosingUri"] {
|
||||
#"C:\path\to\dir"#
|
||||
}
|
||||
["workingDir with relative path"] {
|
||||
#"C:\path\to\dir"#
|
||||
}
|
||||
["workingDir with absolute path"] {
|
||||
#"C:\foo\bar"#
|
||||
}
|
||||
["UNC path"] {
|
||||
#"\\server\share\path\to\foo"#
|
||||
}
|
||||
["UNC path 2"] {
|
||||
#"\\server\share\new\path"#
|
||||
}
|
||||
["empty path"] {
|
||||
#"C:\path\to\dir"#
|
||||
}
|
||||
["multiple consecutive slashes"] {
|
||||
#"C:\path\to\dir\my\path"#
|
||||
}
|
||||
["different drive letter"] {
|
||||
#"D:\my\path"#
|
||||
}
|
||||
["UNC path as base with relative path"] {
|
||||
#"\\server\share\dir\my\path"#
|
||||
}
|
||||
["UNC path as base with .."] {
|
||||
#"\\server\share\dir\my\path"#
|
||||
}
|
||||
["UNC path as base with ."] {
|
||||
#"\\server\share\dir"#
|
||||
}
|
||||
["UNC path as base, cannot remove root"] {
|
||||
#"\\server\share\"#
|
||||
}
|
||||
["UNC path as base, cannot remove root 2"] {
|
||||
#"\\server\share\"#
|
||||
}
|
||||
["UNC path as base with drive letter path"] {
|
||||
#"C:\local\path"#
|
||||
}
|
||||
["UNC path as base with no drive"] {
|
||||
#"\\server\\my\path"#
|
||||
}
|
||||
["trailing separator preserved"] {
|
||||
#"C:\path\to\dir\my\path"#
|
||||
}
|
||||
["trailing separator preserved with normalization"] {
|
||||
#"C:\path\to\my\path"#
|
||||
}
|
||||
["no trailing separator"] {
|
||||
#"C:\path\to\dir\my\path"#
|
||||
}
|
||||
["complex normalization"] {
|
||||
#"C:\path\to\dir\bar\baz"#
|
||||
}
|
||||
["UNC with forward slashes"] {
|
||||
#"\\server\share\dir\my\path"#
|
||||
}
|
||||
["absolute path with different drive and forward slashes"] {
|
||||
#"D:\my\path"#
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
examples {
|
||||
["resolvedEvaluatorSettings"] {
|
||||
new {
|
||||
modulePath {
|
||||
"/path/to/modulepath/first"
|
||||
"/path/to/modulepath/second"
|
||||
}
|
||||
moduleCacheDir = "/path/to/path/to/cache"
|
||||
externalModuleReaders {
|
||||
["relative path"] {
|
||||
executable = "/path/to/foo/executable"
|
||||
workingDir = "/path/to"
|
||||
}
|
||||
["absolute path"] {
|
||||
executable = "/path/to/executable"
|
||||
workingDir = "/path/to"
|
||||
}
|
||||
["command name"] {
|
||||
executable = "foo"
|
||||
workingDir = "/path/to"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
–– Pkl Error ––
|
||||
URI `file:///not a valid uri` has invalid syntax.
|
||||
|
||||
xxx | moduleCacheDir = resolvePath(enclosingUri, module.moduleCacheDir!!, forWindows)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at pkl.EvaluatorSettings#resolveForOs.moduleCacheDir (pkl:EvaluatorSettings)
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
–– Pkl Error ––
|
||||
Type constraint `(externalModuleReaders != null).implies(isFileBasedProject)` violated.
|
||||
Value: new ModuleClass { externalProperties = ?; env = ?; allowedModules = ?; allowe...
|
||||
|
||||
(externalModuleReaders != null).implies(isFileBasedProject)
|
||||
│ │ │ │
|
||||
│ true false false
|
||||
new Mapping { ["foo"] { executable = ?; arguments = ?; workingDir = ? } }
|
||||
|
||||
xxx | (externalModuleReaders != null).implies(isFileBasedProject),
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at pkl.Project#evaluatorSettings (pkl:Project)
|
||||
|
||||
x | evaluatorSettings {
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
at PklProject#evaluatorSettings (file:///$snippetsDir/input/projects/badPklProject5/PklProject)
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
–– Pkl Error ––
|
||||
Refusing to read resource `file:///file.txt` because it is not within the root directory (`--root-dir`).
|
||||
|
||||
x | res = read("file:///file.txt")
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at badRead#res (file:///$snippetsDir/input/projects/evaluatorSettings2/badRead.pkl)
|
||||
|
||||
xxx | renderer.renderDocument(value)
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
at pkl.base#Module.output.text (pkl:base)
|
||||
|
||||
xxx | if (renderer is BytesRenderer) renderer.renderDocument(value) else text.encodeToBytes("UTF-8")
|
||||
^^^^
|
||||
at pkl.base#Module.output.bytes (pkl:base)
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
res = """
|
||||
res = read("file:///file.txt")
|
||||
|
||||
"""
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright © 2024-2025 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
* Copyright © 2024-2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -15,20 +15,55 @@
|
||||
*/
|
||||
package org.pkl.core.module
|
||||
|
||||
import java.io.File
|
||||
import java.net.URI
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.io.path.createParentDirectories
|
||||
import kotlin.io.path.outputStream
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Assumptions.assumeTrue
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.io.TempDir
|
||||
import org.pkl.commons.test.FileTestUtils
|
||||
import org.pkl.commons.toPath
|
||||
import org.pkl.commons.writeString
|
||||
import org.pkl.core.EvaluatorBuilder
|
||||
import org.pkl.core.ModuleSource
|
||||
import org.pkl.core.SecurityManagers
|
||||
import org.pkl.core.externalreader.*
|
||||
import org.pkl.core.evaluatorSettings.PklEvaluatorSettings
|
||||
import org.pkl.core.externalreader.ExternalReaderProcess
|
||||
import org.pkl.core.externalreader.TestExternalModuleReader
|
||||
import org.pkl.core.externalreader.TestExternalReaderProcess
|
||||
import org.pkl.core.resource.ResourceReaders
|
||||
import org.pkl.core.util.IoUtils
|
||||
|
||||
class ModuleKeyFactoriesTest {
|
||||
companion object {
|
||||
private val externalReaderFixture by lazy {
|
||||
val readerPath =
|
||||
"pkl-core/build/fixtures/externalreader".let { if (IoUtils.isWindows()) "$it.bat" else it }
|
||||
|
||||
FileTestUtils.rootProjectDir.resolve(readerPath).also { path ->
|
||||
if (!Files.exists(path)) {
|
||||
throw AssertionError(
|
||||
"Fixture `externalreader` not found. To fix this problem, first run" +
|
||||
" `./gradlew pkl-core:externalReaderFixture`."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private fun pathEnvIsSet(): Boolean {
|
||||
return System.getenv("PATH")
|
||||
?.split(File.pathSeparator)
|
||||
?.contains(externalReaderFixture.toAbsolutePath().toString()) ?: false
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `standard library`() {
|
||||
val factory = ModuleKeyFactories.standardLibrary
|
||||
@@ -146,4 +181,34 @@ class ModuleKeyFactoriesTest {
|
||||
proc.close()
|
||||
runtime.close()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `external process -- spawning an executable using a path`() {
|
||||
testExternalReader(externalReaderFixture.toAbsolutePath().toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `external process -- spawning an executable using a simple name off PATH`() {
|
||||
assumeTrue(pathEnvIsSet(), "PATH contains fixtures dir")
|
||||
testExternalReader("externalreader")
|
||||
}
|
||||
|
||||
private fun testExternalReader(executable: String) {
|
||||
val evaluator = makeEvaluatorWithExternalReader(executable)
|
||||
val result = evaluator.use {
|
||||
evaluator.evaluateExpression(ModuleSource.uri("pkl:base"), "read(\"foo:foo\").text")
|
||||
}
|
||||
assertThat(result).isEqualTo("hello")
|
||||
}
|
||||
|
||||
private fun makeEvaluatorWithExternalReader(reader: String) =
|
||||
with(EvaluatorBuilder.preconfigured()) {
|
||||
val process =
|
||||
ExternalReaderProcess.of(PklEvaluatorSettings.ExternalReader(reader, listOf(), null))
|
||||
addModuleKeyFactory(ModuleKeyFactories.externalProcess("foo", process))
|
||||
addResourceReader(ResourceReaders.externalProcess("foo", process))
|
||||
setAllowedModules(allowedModules + listOf(Pattern.compile("foo:")))
|
||||
setAllowedResources(allowedResources + listOf(Pattern.compile("foo:")))
|
||||
build()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package org.pkl.core.project
|
||||
import java.net.URI
|
||||
import java.nio.file.Path
|
||||
import java.util.regex.Pattern
|
||||
import kotlin.io.path.createDirectories
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatCode
|
||||
import org.junit.jupiter.api.Test
|
||||
@@ -153,12 +154,57 @@ class ProjectTest {
|
||||
)
|
||||
val project = Project.loadFromPath(projectPath)
|
||||
assertThat(project.`package`).isEqualTo(expectedPackage)
|
||||
assertThat(project.evaluatorSettings).isEqualTo(expectedSettings)
|
||||
assertThat(project.resolvedEvaluatorSettings).isEqualTo(expectedSettings)
|
||||
assertThat(project.annotations).isEqualTo(expectedAnnotations)
|
||||
assertThat(project.tests)
|
||||
.isEqualTo(listOf(path.resolve("test1.pkl"), path.resolve("test2.pkl")))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `loadFromPath() resolvedEvaluatorSettings`(@TempDir path: Path) {
|
||||
val projectPath =
|
||||
path.resolve("PklProject").also {
|
||||
it.writeString(
|
||||
"""
|
||||
amends "pkl:Project"
|
||||
|
||||
projectFileUri = "file:///path/to/PklProject"
|
||||
|
||||
evaluatorSettings {
|
||||
rootDir = "."
|
||||
moduleCacheDir = "cache/"
|
||||
modulePath {
|
||||
"modulepath1/"
|
||||
"modulepath2/"
|
||||
}
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
val project = Project.loadFromPath(projectPath)
|
||||
assertThat(project.resolvedEvaluatorSettings)
|
||||
.usingRecursiveComparison()
|
||||
.isEqualTo(
|
||||
PklEvaluatorSettings(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
Path.of("/path/to/cache/"),
|
||||
listOf(Path.of("/path/to/modulepath1/"), Path.of("/path/to/modulepath2/")),
|
||||
null,
|
||||
Path.of("/path/to"),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `load wrong type`(@TempDir path: Path) {
|
||||
val projectPath = path.resolve("PklProject")
|
||||
@@ -261,4 +307,59 @@ class ProjectTest {
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `external readers -- executable path is relative to project dir`(@TempDir tempDir: Path) {
|
||||
val projectDir = tempDir.resolve("project").also { it.createDirectories() }
|
||||
val pklProject =
|
||||
projectDir.resolve("PklProject").also {
|
||||
it.writeString(
|
||||
// language=pkl
|
||||
"""
|
||||
amends "pkl:Project"
|
||||
|
||||
evaluatorSettings {
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
executable = "foo/bar/baz"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
val project = Project.loadFromPath(pklProject, SecurityManagers.defaultManager, null)
|
||||
assertThat(project.resolvedEvaluatorSettings.externalModuleReaders).hasSize(1)
|
||||
assertThat(project.resolvedEvaluatorSettings.externalModuleReaders?.get("foo")!!.executable())
|
||||
.isEqualTo(projectDir.resolve("foo/bar/baz").toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `external readers -- executable is unmodified simple name`(@TempDir tempDir: Path) {
|
||||
val projectDir = tempDir.resolve("project").also { it.createDirectories() }
|
||||
val pklProject =
|
||||
projectDir.resolve("PklProject").also {
|
||||
it.writeString(
|
||||
// language=pkl
|
||||
"""
|
||||
amends "pkl:Project"
|
||||
|
||||
evaluatorSettings {
|
||||
externalModuleReaders {
|
||||
["foo"] {
|
||||
executable = "my-command"
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
.trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
val project = Project.loadFromPath(pklProject, SecurityManagers.defaultManager, null)
|
||||
assertThat(project.evaluatorSettings.externalModuleReaders).hasSize(1)
|
||||
assertThat(project.evaluatorSettings.externalModuleReaders?.get("foo")!!.executable())
|
||||
.isEqualTo("my-command")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Copyright © 2026 Apple Inc. and the Pkl project authors. All rights reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pkl.core.util
|
||||
|
||||
import java.net.URI
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class PathResolverTest {
|
||||
private val posix = PathResolvers.forPosix()
|
||||
private val windows = PathResolvers.forWindows()
|
||||
|
||||
@Nested
|
||||
inner class PosixTests {
|
||||
@Test
|
||||
fun `simple relative path appended to file base`() {
|
||||
assertThat(posix.resolvePath(URI("file:///home/user/base.pkl"), "sibling.pkl"))
|
||||
.isEqualTo("/home/user/base.pkl/sibling.pkl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `relative path appended to directory base (trailing slash)`() {
|
||||
assertThat(posix.resolvePath(URI("file:///home/user/dir/"), "file.pkl"))
|
||||
.isEqualTo("/home/user/dir/file.pkl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `nested relative path`() {
|
||||
assertThat(posix.resolvePath(URI("file:///home/user/base.pkl"), "sub/dir/file.pkl"))
|
||||
.isEqualTo("/home/user/base.pkl/sub/dir/file.pkl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `absolute path overrides base`() {
|
||||
assertThat(posix.resolvePath(URI("file:///home/user/base.pkl"), "/absolute/path.pkl"))
|
||||
.isEqualTo("/absolute/path.pkl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `absolute path containing dot is normalized`() {
|
||||
assertThat(posix.resolvePath(URI("file:///home/user/base.pkl"), "/foo/./bar.pkl"))
|
||||
.isEqualTo("/foo/bar.pkl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `absolute path containing double-dot is normalized`() {
|
||||
assertThat(posix.resolvePath(URI("file:///home/user/base.pkl"), "/foo/../bar.pkl"))
|
||||
.isEqualTo("/bar.pkl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `single dot in relative path is elided`() {
|
||||
assertThat(posix.resolvePath(URI("file:///home/user/base.pkl"), "./sibling.pkl"))
|
||||
.isEqualTo("/home/user/base.pkl/sibling.pkl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `double-dot in relative path goes up one segment`() {
|
||||
assertThat(posix.resolvePath(URI("file:///home/user/base.pkl"), "../sibling.pkl"))
|
||||
.isEqualTo("/home/user/sibling.pkl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two double-dots in relative path go up two segments`() {
|
||||
assertThat(posix.resolvePath(URI("file:///home/user/a/b.pkl"), "../../c.pkl"))
|
||||
.isEqualTo("/home/user/c.pkl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `mixed relative path with dot-dot`() {
|
||||
assertThat(posix.resolvePath(URI("file:///home/user/base.pkl"), "sub/dir/../../other.pkl"))
|
||||
.isEqualTo("/home/user/base.pkl/other.pkl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `double-dot beyond root clamps to root`() {
|
||||
assertThat(posix.resolvePath(URI("file:///file.pkl"), "../../root.pkl"))
|
||||
.isEqualTo("/root.pkl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `root base with relative path`() {
|
||||
assertThat(posix.resolvePath(URI("file:///"), "file.pkl")).isEqualTo("/file.pkl")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `URI with percent-encoded path is decoded`() {
|
||||
// URI.getPath() decodes percent-encoding
|
||||
assertThat(posix.resolvePath(URI("file:///home/user%20name/base.pkl"), "file.pkl"))
|
||||
.isEqualTo("/home/user name/base.pkl/file.pkl")
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class WindowsTests {
|
||||
@Test
|
||||
fun `drive letter URI with simple relative path`() {
|
||||
assertThat(windows.resolvePath(URI("file:///C:/Users/user/base.pkl"), "relative.pkl"))
|
||||
.isEqualTo("""C:\Users\user\base.pkl\relative.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `drive letter URI with nested relative path`() {
|
||||
assertThat(windows.resolvePath(URI("file:///C:/Users/user/base.pkl"), "sub\\dir\\file.pkl"))
|
||||
.isEqualTo("""C:\Users\user\base.pkl\sub\dir\file.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `drive letter URI with forward-slash relative path is normalised to backslash`() {
|
||||
assertThat(windows.resolvePath(URI("file:///C:/Users/user/base.pkl"), "sub/dir/file.pkl"))
|
||||
.isEqualTo("""C:\Users\user\base.pkl\sub\dir\file.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `drive letter URI with directory base (trailing backslash)`() {
|
||||
assertThat(windows.resolvePath(URI("file:///C:/Users/dir/"), "file.pkl"))
|
||||
.isEqualTo("""C:\Users\dir\file.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `backslash dot in relative path is elided`() {
|
||||
assertThat(windows.resolvePath(URI("file:///C:/Users/user/base.pkl"), """..\sibling.pkl"""))
|
||||
.isEqualTo("""C:\Users\user\sibling.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `forward-slash dot-dot in relative path is normalised`() {
|
||||
assertThat(windows.resolvePath(URI("file:///C:/Users/user/base.pkl"), "../sibling.pkl"))
|
||||
.isEqualTo("""C:\Users\user\sibling.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `backslash single-dot in relative path is elided`() {
|
||||
assertThat(windows.resolvePath(URI("file:///C:/Users/user/base.pkl"), """.\\sibling.pkl"""))
|
||||
.isEqualTo("""C:\Users\user\base.pkl\sibling.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `two double-dots go up two segments`() {
|
||||
// join: C:\Users\user\a\b.pkl\..\..\c.pkl -> C:\Users\user\c.pkl
|
||||
assertThat(windows.resolvePath(URI("file:///C:/Users/user/a/b.pkl"), "..\\..\\c.pkl"))
|
||||
.isEqualTo("""C:\Users\user\c.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `double-dot beyond drive root clamps to root`() {
|
||||
assertThat(windows.resolvePath(URI("file:///C:/base.pkl"), "..\\..\\out.pkl"))
|
||||
.isEqualTo("""C:\out.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `absolute path on same drive overrides base`() {
|
||||
assertThat(windows.resolvePath(URI("file:///C:/Users/base.pkl"), """C:\other\path.pkl"""))
|
||||
.isEqualTo("""C:\other\path.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `absolute path on different drive overrides base`() {
|
||||
assertThat(windows.resolvePath(URI("file:///C:/Users/base.pkl"), """D:\other.pkl"""))
|
||||
.isEqualTo("""D:\other.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `absolute path with forward slashes is accepted`() {
|
||||
assertThat(windows.resolvePath(URI("file:///C:/Users/base.pkl"), "D:/other.pkl"))
|
||||
.isEqualTo("""D:\other.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `root-relative backslash path takes drive root from base`() {
|
||||
// \root.pkl is root-relative; drive letter is inherited from base
|
||||
assertThat(windows.resolvePath(URI("file:///C:/Users/base.pkl"), """\root.pkl"""))
|
||||
.isEqualTo("""C:\root.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `root-relative forward-slash path takes drive root from base`() {
|
||||
assertThat(windows.resolvePath(URI("file:///C:/Users/base.pkl"), "/root.pkl"))
|
||||
.isEqualTo("""C:\root.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `UNC URI with simple relative path`() {
|
||||
assertThat(windows.resolvePath(URI("file://server/share/base.pkl"), "relative.pkl"))
|
||||
.isEqualTo("""\\server\share\base.pkl\relative.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `UNC URI with double-dot goes up within share`() {
|
||||
assertThat(windows.resolvePath(URI("file://server/share/dir/base.pkl"), """..\sibling.pkl"""))
|
||||
.isEqualTo("""\\server\share\dir\sibling.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `UNC URI double-dot beyond share root clamps to share root`() {
|
||||
assertThat(windows.resolvePath(URI("file://server/share/base.pkl"), "..\\..\\.\\out.pkl"))
|
||||
.isEqualTo("""\\server\share\out.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `UNC URI with absolute UNC path overrides base`() {
|
||||
assertThat(
|
||||
windows.resolvePath(URI("file://server/share/base.pkl"), """\\other\share\file.pkl""")
|
||||
)
|
||||
.isEqualTo("""\\other\share\file.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `absolute path containing dot is normalized`() {
|
||||
assertThat(windows.resolvePath(URI("file:///C:/Users/base.pkl"), """C:\foo\.\bar.pkl"""))
|
||||
.isEqualTo("""C:\foo\bar.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `absolute path containing double-dot is normalized`() {
|
||||
assertThat(windows.resolvePath(URI("file:///C:/Users/base.pkl"), """C:\foo\..\bar.pkl"""))
|
||||
.isEqualTo("""C:\bar.pkl""")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `file URI without drive letter`() {
|
||||
assertThat(windows.resolvePath(URI("file:///path/to/foo"), "bar"))
|
||||
.isEqualTo("""\path\to\foo\bar""")
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user