release 0.1.0
This commit is contained in:
commit
30d94536a9
90 changed files with 7722 additions and 0 deletions
6
modules/std/main.owa
Normal file
6
modules/std/main.owa
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
(seq
|
||||
(include "platform")
|
||||
(include "path")
|
||||
|
||||
(test.space "std" (include "tests/main"))
|
||||
)
|
||||
222
modules/std/path.owa
Normal file
222
modules/std/path.owa
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
(namespace path
|
||||
;; Platform-specific path separator.
|
||||
(def sep (match platform.os-family
|
||||
(:windows "\\")
|
||||
(_ "/")
|
||||
))
|
||||
|
||||
;; Internal: converts "/" to "\" on Windows; no-op on other platforms.
|
||||
(fn norm-seps <Str>[<Str>p]
|
||||
(match platform.os-family
|
||||
(:windows (str.map (lambda [c _] (if (eq? c "/") "\\" c)) p))
|
||||
(_ p)))
|
||||
|
||||
;; Returns the root prefix of a path.
|
||||
;;
|
||||
;; Unix — "/" for paths starting with "/", "" for relative.
|
||||
;; Windows — "\" for UNC/rooted paths ("\" or "/" prefix)
|
||||
;; "X:\" for drive-letter absolute paths ("X:\" or "X:/")
|
||||
;; "" for all relative paths ("X:path" is relative on Windows)
|
||||
(fn root <Str>[<Str>p]
|
||||
(match platform.os-family
|
||||
(:windows
|
||||
(if (bool.or (str.starts-with? p "\\") (str.starts-with? p "/"))
|
||||
"\\"
|
||||
(if (bool.and (gte? (str.len p) 3)
|
||||
(bool.and (eq? (str.chars-at p 1) ":")
|
||||
(bool.or (eq? (str.chars-at p 2) "\\")
|
||||
(eq? (str.chars-at p 2) "/"))))
|
||||
(str.concat (str.take p 2) "\\")
|
||||
"")))
|
||||
(_ (if (str.starts-with? p "/") "/" ""))
|
||||
))
|
||||
|
||||
;; Returns true if the path is absolute (has a non-empty root prefix).
|
||||
(fn absolute? <Bool>[<Str>p]
|
||||
(bool.not (str.empty? (root p))))
|
||||
|
||||
;; Returns true if the path is relative (has no root prefix).
|
||||
(fn relative? <Bool>[<Str>p]
|
||||
(bool.not (absolute? p)))
|
||||
|
||||
;; Splits a path into its string components using the platform separator.
|
||||
;; On Windows "/" is also accepted as a separator.
|
||||
;; The root prefix (if any) is preserved as the first component so that
|
||||
;; split + join is a round-trip operation.
|
||||
(fn split <Vec>[<Str>p]
|
||||
(if (str.empty? p)
|
||||
[]
|
||||
(str.split (norm-seps p) sep)))
|
||||
|
||||
;; Joins path components with the platform separator.
|
||||
(fn join <Str>[<Vec>p]
|
||||
(str.join sep p))
|
||||
|
||||
;; Removes "." components, resolves ".." components, and returns the
|
||||
;; canonical form of the path using the platform separator.
|
||||
;;
|
||||
;; Bug fixes vs. previous version:
|
||||
;; • absolute paths that collapse entirely return the root (e.g. "/" not "")
|
||||
;; • Windows drive-letter paths no longer get a spurious leading "\"
|
||||
(fn normalize <Str>[<Str>p]
|
||||
(seq
|
||||
(def r (root p))
|
||||
;; Work only on the non-root body so that root components never
|
||||
;; participate in ".." resolution.
|
||||
(def body (norm-seps (str.drop p (str.len r))))
|
||||
(def parts (if (str.empty? body) [] (str.split body sep)))
|
||||
(def normalized [])
|
||||
(for part parts
|
||||
(if (bool.or (eq? part ".") (str.empty? part)) (continue))
|
||||
(set! normalized (if (eq? part "..")
|
||||
(vec.drop-last normalized)
|
||||
(vec.conj normalized part))))
|
||||
(def result (join normalized))
|
||||
(if (str.empty? r)
|
||||
result
|
||||
(if (str.empty? result)
|
||||
r
|
||||
(str.concat r result)))))
|
||||
|
||||
;; Returns the parent directory of the path.
|
||||
;; • Paths directly under the root return the root string.
|
||||
;; • Top-level relative paths (e.g. "a") return "".
|
||||
;;
|
||||
;; Bug fix vs. previous version: returns sep-based root, not hardcoded "/".
|
||||
(fn parent <Str>[<Str>p] (seq
|
||||
(def norm (normalize p))
|
||||
(def r (root norm))
|
||||
(def body (str.drop norm (str.len r)))
|
||||
(def parts (if (str.empty? body)
|
||||
[]
|
||||
(vec.filter
|
||||
(lambda [part _] (bool.not (str.empty? part)))
|
||||
(str.split body sep))))
|
||||
(def parent-parts (vec.drop-last parts))
|
||||
(if (vec.empty? parent-parts)
|
||||
r
|
||||
(str.concat r (join parent-parts)))))
|
||||
|
||||
;; Returns the last component of the path (filename or final directory name).
|
||||
;; Returns "" for paths that end with a separator.
|
||||
(fn basename <Str>[<Str>p] (seq
|
||||
(def parts (split p))
|
||||
(if (vec.empty? parts) "" (vec.last parts))))
|
||||
|
||||
;; Returns the file extension including the leading dot, or "" if none.
|
||||
;;
|
||||
;; Rules:
|
||||
;; • Hidden files whose name starts with "." have no extension.
|
||||
;; • A trailing dot (e.g. "file.") is not considered an extension.
|
||||
;; • Only the last dot in the basename is used (e.g. "a.tar.gz" → ".gz").
|
||||
;;
|
||||
;; Examples:
|
||||
;; "file.txt" → ".txt"
|
||||
;; "archive.tar.gz" → ".gz"
|
||||
;; "file" → ""
|
||||
;; ".hidden" → ""
|
||||
;; "file." → ""
|
||||
(fn ext <Str>[<Str>p] (seq
|
||||
(def base (basename p))
|
||||
(def dot-idx (vec.fold
|
||||
(lambda [acc elem idx]
|
||||
(if (eq? elem ".") idx acc))
|
||||
-1
|
||||
(vec.from base)))
|
||||
(if (bool.or (lte? dot-idx 0) (eq? dot-idx (-- (str.len base))))
|
||||
""
|
||||
(str.drop base dot-idx))))
|
||||
|
||||
;; Returns the filename without its extension.
|
||||
;;
|
||||
;; Examples:
|
||||
;; "file.txt" → "file"
|
||||
;; "archive.tar.gz" → "archive.tar"
|
||||
;; "file" → "file"
|
||||
;; ".hidden" → ".hidden"
|
||||
(fn stem <Str>[<Str>p] (seq
|
||||
(def base (basename p))
|
||||
(def e (ext p))
|
||||
(if (str.empty? e)
|
||||
base
|
||||
(str.take base (- (str.len base) (str.len e))))))
|
||||
|
||||
;; Returns the path with its extension replaced by new-ext.
|
||||
;; Pass "" as new-ext to strip the extension entirely.
|
||||
;; The directory prefix and stem are preserved unchanged.
|
||||
;;
|
||||
;; Examples:
|
||||
;; (with-ext "file.txt" ".md") → "file.md"
|
||||
;; (with-ext "file.txt" "") → "file"
|
||||
;; (with-ext "file" ".txt") → "file.txt"
|
||||
(fn with-ext <Str>[<Str>p <Str>new-ext] (seq
|
||||
(def e (ext p))
|
||||
(def without-ext
|
||||
(if (str.empty? e)
|
||||
p
|
||||
(str.take p (- (str.len p) (str.len e)))))
|
||||
(str.concat without-ext new-ext)))
|
||||
|
||||
;; Resolves path relative to base and returns the normalized result.
|
||||
;; If path is already absolute it is returned normalized, ignoring base.
|
||||
;; Otherwise base + sep + path is normalized.
|
||||
;;
|
||||
;; Examples (Unix):
|
||||
;; (resolve "/base" "file.txt") → "/base/file.txt"
|
||||
;; (resolve "/base/dir" "../file.txt") → "/base/file.txt"
|
||||
;; (resolve "/other" "/abs") → "/abs"
|
||||
(fn resolve <Str>[<Str>base <Str>p]
|
||||
(if (absolute? p)
|
||||
(normalize p)
|
||||
(normalize (str.concat base sep p))))
|
||||
|
||||
;; Computes the relative path from base to target.
|
||||
;;
|
||||
;; • Returns "." when base and target are the same path.
|
||||
;; • When the roots differ (e.g. different Windows drive letters)
|
||||
;; the normalized target is returned unchanged.
|
||||
;;
|
||||
;; Examples (Unix):
|
||||
;; (relative-to "/a/b" "/a/b/c/d") → "c/d"
|
||||
;; (relative-to "/a/b" "/a/c") → "../c"
|
||||
;; (relative-to "/a/b/c" "/a") → "../.."
|
||||
;; (relative-to "/a/b" "/a/b") → "."
|
||||
(fn relative-to <Str>[<Str>base <Str>target] (seq
|
||||
(def norm-base (normalize base))
|
||||
(def norm-target (normalize target))
|
||||
(def base-r (root norm-base))
|
||||
(def target-r (root norm-target))
|
||||
(if (nq? base-r target-r)
|
||||
norm-target
|
||||
(seq
|
||||
(def base-body (str.drop norm-base (str.len base-r)))
|
||||
(def target-body (str.drop norm-target (str.len target-r)))
|
||||
(fn parts-of [body]
|
||||
(if (str.empty? body)
|
||||
[]
|
||||
(vec.filter
|
||||
(lambda [part _] (bool.not (str.empty? part)))
|
||||
(str.split body sep))))
|
||||
(def base-parts (parts-of base-body))
|
||||
(def target-parts (parts-of target-body))
|
||||
(def base-len (vec.len base-parts))
|
||||
(def target-len (vec.len target-parts))
|
||||
;; Find length of the common prefix.
|
||||
(def i 0)
|
||||
(while (bool.and (lt? i base-len)
|
||||
(bool.and (lt? i target-len)
|
||||
(eq? (vec.get base-parts i)
|
||||
(vec.get target-parts i))))
|
||||
(set! i (++ i)))
|
||||
;; One ".." per remaining component in base, then the remaining target components.
|
||||
(def ups (vec.map (lambda [_e _i] "..") (vec.drop base-parts i)))
|
||||
(def remaining (vec.drop target-parts i))
|
||||
(def result-parts
|
||||
(vec.fold
|
||||
(lambda [acc elem _] (vec.conj acc elem))
|
||||
ups
|
||||
remaining))
|
||||
(if (vec.empty? result-parts)
|
||||
"."
|
||||
(join result-parts))))))
|
||||
)
|
||||
55
modules/std/platform.owa
Normal file
55
modules/std/platform.owa
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
(namespace platform
|
||||
(def os-name builtins.platform.os-name)
|
||||
(def os-family builtins.platform.os-family)
|
||||
(def arch builtins.platform.arch)
|
||||
(def argv builtins.platform.argv)
|
||||
(def executable #(vec.get (argv) 0))
|
||||
(def args #(vec.slice (argv) 1 (vec.len (argv))))
|
||||
|
||||
(def debug? (lookup __is_debug__ false))
|
||||
|
||||
(def os-names [
|
||||
:linux
|
||||
:windows
|
||||
:macos
|
||||
:android
|
||||
:ios
|
||||
:openbsd
|
||||
:freebsd
|
||||
:netbsd
|
||||
:wasi
|
||||
:hermit
|
||||
:aix
|
||||
:apple
|
||||
:dragonfly
|
||||
:emscripten
|
||||
:espidf
|
||||
:fortanix
|
||||
:uefi
|
||||
:fuchsia
|
||||
:haiku
|
||||
:hermit
|
||||
:watchos
|
||||
:visionos
|
||||
:tvos
|
||||
:horizon
|
||||
:hurd
|
||||
:illumos
|
||||
:l4re
|
||||
:nto
|
||||
:redox
|
||||
:solaris
|
||||
:solid_asp3
|
||||
:vexos
|
||||
:vita
|
||||
:vxworks
|
||||
:xous
|
||||
])
|
||||
|
||||
(def os-families [
|
||||
:unix
|
||||
:windows
|
||||
:itron
|
||||
:wasm
|
||||
])
|
||||
)
|
||||
3
modules/std/tests/main.owa
Normal file
3
modules/std/tests/main.owa
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
(seq
|
||||
(include "path")
|
||||
)
|
||||
286
modules/std/tests/path.owa
Normal file
286
modules/std/tests/path.owa
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
(namespace test.path
|
||||
(test.case "path.sep"
|
||||
(assert.eq! path.sep (match platform.os-family (:windows "\\") (_ "/")))
|
||||
)
|
||||
|
||||
(test.case "path.root"
|
||||
;; Relative paths and empty string have no root on all platforms.
|
||||
(assert.eq! (path.root "relative") "")
|
||||
(assert.eq! (path.root "") "")
|
||||
(match platform.os-family
|
||||
(:windows (seq
|
||||
;; Rooted by backslash or forward-slash → canonical "\"
|
||||
(assert.eq! (path.root "\\a\\b") "\\")
|
||||
(assert.eq! (path.root "/a/b") "\\")
|
||||
;; Drive-letter absolute ("X:\" or "X:/")
|
||||
(assert.eq! (path.root "C:\\a\\b") "C:\\")
|
||||
(assert.eq! (path.root "C:/a/b") "C:\\")
|
||||
(assert.eq! (path.root "C:\\") "C:\\")
|
||||
;; Drive letter WITHOUT a separator is relative on Windows
|
||||
(assert.eq! (path.root "C:") "")
|
||||
(assert.eq! (path.root "C:relative") "")
|
||||
))
|
||||
(_ (seq
|
||||
(assert.eq! (path.root "/") "/")
|
||||
(assert.eq! (path.root "/a/b") "/")
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
(test.case "path.absolute?"
|
||||
;; "/" is recognised as absolute on all platforms.
|
||||
(assert.ok! (path.absolute? "/absolute/unix"))
|
||||
(assert.ok! (path.absolute? "/"))
|
||||
(assert.not! (path.absolute? "relative/unix"))
|
||||
(assert.not! (path.absolute? "./relative/unix"))
|
||||
(match platform.os-family
|
||||
(:windows (seq
|
||||
;; Drive-letter prefix (both separators)
|
||||
(assert.ok! (path.absolute? "C:\\absolute\\windows"))
|
||||
(assert.ok! (path.absolute? "C:/absolute/windows"))
|
||||
;; UNC / rooted backslash
|
||||
(assert.ok! (path.absolute? "\\absolute\\unc"))
|
||||
;; Relative Windows paths
|
||||
(assert.not! (path.absolute? "relative\\windows"))
|
||||
(assert.not! (path.absolute? ".\\relative\\windows"))
|
||||
;; Drive letter without separator is relative
|
||||
(assert.not! (path.absolute? "C:relative"))
|
||||
))
|
||||
(_ (void))
|
||||
)
|
||||
)
|
||||
|
||||
(test.case "path.relative?"
|
||||
(assert.ok! (path.relative? "relative/unix"))
|
||||
(assert.not! (path.relative? "/absolute/unix"))
|
||||
(match platform.os-family
|
||||
(:windows (seq
|
||||
(assert.ok! (path.relative? "relative\\windows"))
|
||||
(assert.not! (path.relative? "C:\\absolute\\windows"))
|
||||
(assert.not! (path.relative? "\\absolute\\unc"))
|
||||
(assert.ok! (path.relative? "C:relative"))
|
||||
))
|
||||
(_ (void))
|
||||
)
|
||||
)
|
||||
|
||||
(test.case "path.split"
|
||||
(assert.eq! (path.split "") [])
|
||||
(assert.eq! (path.split "a") ["a"])
|
||||
(match platform.os-family
|
||||
(:windows (seq
|
||||
(assert.eq! (path.split "a\\b\\c") ["a" "b" "c"])
|
||||
;; Windows also accepts "/" as separator
|
||||
(assert.eq! (path.split "a/b/c") ["a" "b" "c"])
|
||||
;; Drive-letter prefix is kept as the first component (round-trip with join)
|
||||
(assert.eq! (path.split "C:\\a\\b") ["C:" "a" "b"])
|
||||
(assert.eq! (path.split "C:/a/b") ["C:" "a" "b"])
|
||||
))
|
||||
(_ (seq
|
||||
(assert.eq! (path.split "a/b/c") ["a" "b" "c"])
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
(test.case "path.join"
|
||||
(assert.eq! (path.join []) "")
|
||||
(assert.eq! (path.join ["a"]) "a")
|
||||
(match platform.os-family
|
||||
(:windows (seq
|
||||
(assert.eq! (path.join ["a" "b" "c"]) "a\\b\\c")
|
||||
(assert.eq! (path.join ["C:" "a" "b"]) "C:\\a\\b")
|
||||
))
|
||||
(_ (seq
|
||||
(assert.eq! (path.join ["a" "b" "c"]) (str.concat "a" path.sep "b" path.sep "c"))
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
(test.case "path.normalize"
|
||||
(match platform.os-family
|
||||
(:windows (seq
|
||||
;; Dot component is stripped
|
||||
(assert.eq! (path.normalize "a\\.\\b") (path.join ["a" "b"]))
|
||||
;; Double-dot collapses parent
|
||||
(assert.eq! (path.normalize "a\\..\\b") "b")
|
||||
;; Rooted-backslash absolute path
|
||||
(assert.eq! (path.normalize "\\a\\..\\b") (str.concat path.sep "b"))
|
||||
;; Bug fix: absolute path collapsing to root returns the root, not ""
|
||||
(assert.eq! (path.normalize "\\") "\\")
|
||||
(assert.eq! (path.normalize "\\a\\..") "\\")
|
||||
;; Drive-letter paths: no spurious leading "\" added
|
||||
(assert.eq! (path.normalize "C:\\a\\..\\b") "C:\\b")
|
||||
(assert.eq! (path.normalize "C:\\") "C:\\")
|
||||
(assert.eq! (path.normalize "C:\\a\\b\\..\\..") "C:\\")
|
||||
;; Windows normalises "/" separators as well
|
||||
(assert.eq! (path.normalize "a/./b") (path.join ["a" "b"]))
|
||||
(assert.eq! (path.normalize "C:/a/../b") "C:\\b")
|
||||
))
|
||||
(_ (seq
|
||||
;; Dot component is stripped
|
||||
(assert.eq! (path.normalize "a/./b") (path.join ["a" "b"]))
|
||||
;; Double-dot collapses parent
|
||||
(assert.eq! (path.normalize "a/../b") "b")
|
||||
;; Rooted absolute path
|
||||
(assert.eq! (path.normalize "/a/../b") (str.concat path.sep "b"))
|
||||
;; Bug fix: absolute path collapsing to root returns "/", not ""
|
||||
(assert.eq! (path.normalize "/") "/")
|
||||
(assert.eq! (path.normalize "/a/..") "/")
|
||||
(assert.eq! (path.normalize "/a/b/../..") "/")
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
(test.case "path.parent"
|
||||
;; Top-level relative path always returns "" on every platform.
|
||||
(assert.eq! (path.parent "a") "")
|
||||
(match platform.os-family
|
||||
(:windows (seq
|
||||
(assert.eq! (path.parent "a\\b\\c") "a\\b")
|
||||
(assert.eq! (path.parent "a\\b") "a")
|
||||
;; Bug fix: rooted paths return "\" not hardcoded "/"
|
||||
(assert.eq! (path.parent "\\") "\\")
|
||||
(assert.eq! (path.parent "\\..\\a\\.\\..\\") "\\")
|
||||
;; Drive-letter paths
|
||||
(assert.eq! (path.parent "C:\\") "C:\\")
|
||||
(assert.eq! (path.parent "C:\\a") "C:\\")
|
||||
(assert.eq! (path.parent "C:\\a\\b") "C:\\a")
|
||||
(assert.eq! (path.parent "C:\\a\\b\\c") "C:\\a\\b")
|
||||
))
|
||||
(_ (seq
|
||||
(assert.eq! (path.parent "a/b/c") "a/b")
|
||||
(assert.eq! (path.parent "a/b") "a")
|
||||
(assert.eq! (path.parent "/") "/")
|
||||
(assert.eq! (path.parent "/../a/./../") "/")
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
(test.case "path.basename"
|
||||
(assert.eq! (path.basename "file.txt") "file.txt")
|
||||
(match platform.os-family
|
||||
(:windows (seq
|
||||
(assert.eq! (path.basename "a\\b\\c") "c")
|
||||
;; Trailing separator → empty basename
|
||||
(assert.eq! (path.basename "a\\b\\") "")
|
||||
(assert.eq! (path.basename "C:\\Users\\file.txt") "file.txt")
|
||||
;; Windows also handles "/" separator
|
||||
(assert.eq! (path.basename "a/b/c") "c")
|
||||
))
|
||||
(_ (seq
|
||||
(assert.eq! (path.basename "a/b/c") "c")
|
||||
(assert.eq! (path.basename "a/b/") "")
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
(test.case "path.ext"
|
||||
;; Basic cases work on all platforms (basename is a simple name)
|
||||
(assert.eq! (path.ext "file.txt") ".txt")
|
||||
(assert.eq! (path.ext "file") "")
|
||||
(assert.eq! (path.ext ".hidden") "")
|
||||
(assert.eq! (path.ext "archive.tar.gz") ".gz")
|
||||
(assert.eq! (path.ext "file.") "")
|
||||
(assert.eq! (path.ext "") "")
|
||||
(match platform.os-family
|
||||
(:windows (seq
|
||||
(assert.eq! (path.ext "C:\\Users\\file.txt") ".txt")
|
||||
(assert.eq! (path.ext "a\\b\\no-ext") "")
|
||||
))
|
||||
(_ (seq
|
||||
(assert.eq! (path.ext "a/b/file.txt") ".txt")
|
||||
(assert.eq! (path.ext "a/b/no-ext") "")
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
(test.case "path.stem"
|
||||
(assert.eq! (path.stem "file.txt") "file")
|
||||
(assert.eq! (path.stem "file") "file")
|
||||
(assert.eq! (path.stem ".hidden") ".hidden")
|
||||
(assert.eq! (path.stem "archive.tar.gz") "archive.tar")
|
||||
(assert.eq! (path.stem "") "")
|
||||
(match platform.os-family
|
||||
(:windows (seq
|
||||
(assert.eq! (path.stem "C:\\Users\\file.txt") "file")
|
||||
))
|
||||
(_ (seq
|
||||
(assert.eq! (path.stem "a/b/file.txt") "file")
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
(test.case "path.with-ext"
|
||||
(assert.eq! (path.with-ext "file.txt" ".md") "file.md")
|
||||
(assert.eq! (path.with-ext "file.txt" "") "file")
|
||||
(assert.eq! (path.with-ext "file" ".txt") "file.txt")
|
||||
(assert.eq! (path.with-ext "archive.tar.gz" ".bz2") "archive.tar.bz2")
|
||||
;; Hidden file: no extension → new-ext is appended
|
||||
(assert.eq! (path.with-ext ".hidden" ".txt") ".hidden.txt")
|
||||
;; Directory prefix is preserved
|
||||
(match platform.os-family
|
||||
(:windows (seq
|
||||
(assert.eq! (path.with-ext "C:\\a\\file.txt" ".md") "C:\\a\\file.md")
|
||||
))
|
||||
(_ (seq
|
||||
(assert.eq! (path.with-ext "a/b/file.txt" ".md") "a/b/file.md")
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
(test.case "path.resolve"
|
||||
(match platform.os-family
|
||||
(:windows (seq
|
||||
;; Relative path is joined to base and normalized
|
||||
(assert.eq! (path.resolve "C:\\base" "file.txt") "C:\\base\\file.txt")
|
||||
;; ".." is resolved against base
|
||||
(assert.eq! (path.resolve "C:\\base\\dir" "..\\file.txt") "C:\\base\\file.txt")
|
||||
;; Absolute path ignores base
|
||||
(assert.eq! (path.resolve "C:\\other" "C:\\absolute") "C:\\absolute")
|
||||
;; Deep traversal
|
||||
(assert.eq! (path.resolve "C:\\a\\b\\c" "..\\..\\d") "C:\\a\\d")
|
||||
))
|
||||
(_ (seq
|
||||
(assert.eq! (path.resolve "/base" "file.txt") "/base/file.txt")
|
||||
(assert.eq! (path.resolve "/base/dir" "../file.txt") "/base/file.txt")
|
||||
;; Absolute path ignores base
|
||||
(assert.eq! (path.resolve "/other" "/absolute") "/absolute")
|
||||
;; Deep traversal
|
||||
(assert.eq! (path.resolve "/a/b/c" "../../d") "/a/d")
|
||||
;; Relative base + relative path
|
||||
(assert.eq! (path.resolve "base/dir" "file.txt") "base/dir/file.txt")
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
(test.case "path.relative-to"
|
||||
(match platform.os-family
|
||||
(:windows (seq
|
||||
;; Child path
|
||||
(assert.eq! (path.relative-to "C:\\a\\b" "C:\\a\\b\\c\\d") "c\\d")
|
||||
;; Sibling path
|
||||
(assert.eq! (path.relative-to "C:\\a\\b" "C:\\a\\c") "..\\c")
|
||||
;; Ancestor path
|
||||
(assert.eq! (path.relative-to "C:\\a\\b\\c" "C:\\a") "..\\..")
|
||||
;; Same path → "."
|
||||
(assert.eq! (path.relative-to "C:\\a\\b" "C:\\a\\b") ".")
|
||||
;; From root
|
||||
(assert.eq! (path.relative-to "C:\\" "C:\\a\\b") "a\\b")
|
||||
;; Different drives → return normalized target unchanged
|
||||
(assert.eq! (path.relative-to "C:\\a" "D:\\b") "D:\\b")
|
||||
))
|
||||
(_ (seq
|
||||
(assert.eq! (path.relative-to "/a/b" "/a/b/c/d") "c/d")
|
||||
(assert.eq! (path.relative-to "/a/b" "/a/c") "../c")
|
||||
(assert.eq! (path.relative-to "/a/b/c" "/a") "../..")
|
||||
;; Same path → "."
|
||||
(assert.eq! (path.relative-to "/a/b" "/a/b") ".")
|
||||
;; From root
|
||||
(assert.eq! (path.relative-to "/" "/a/b") "a/b")
|
||||
;; Relative paths
|
||||
(assert.eq! (path.relative-to "a/b" "a/b/c") "c")
|
||||
(assert.eq! (path.relative-to "a/b" "a/c") "../c")
|
||||
))
|
||||
)
|
||||
)
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue