diff --git a/API.md b/API.md index 141dd7a..037686d 100644 --- a/API.md +++ b/API.md @@ -177,7 +177,7 @@ Copies entire file tree from `src` to `dest`. Creates `dest` if needed using [`create-dirs`](#babashka.fs/create-dirs), passing it the `:posix-file-permissions` option. Supports same options as [`copy`](#babashka.fs/copy). Returns `dest` as `Path` -
+ ## `create-dir` ``` clojure @@ -190,9 +190,11 @@ Function. Creates dir using [Files/createDirectory](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Files.html#createDirectory(java.nio.file.Path,java.nio.file.attribute.FileAttribute...)). Does not create parents. + Returns created directory as `Path`. + Options: * `:posix-file-permissions` permission for unix-like systems - + ## `create-dirs` ``` clojure @@ -208,7 +210,7 @@ Creates directories for `path` using [Files/createDirectories](https://docs.orac Options: * `:posix-file-permissions` permission for unix-like systems - + ## `create-file` ``` clojure @@ -222,7 +224,7 @@ Creates empty file at `path` using [Files/createFile](https://docs.oracle.com/en Options: * `:posix-file-permissions` string format for posix file permissions is described in the [`str->posix`](#babashka.fs/str->posix) docstring. - + ## `create-link` ``` clojure @@ -232,7 +234,7 @@ Creates empty file at `path` using [Files/createFile](https://docs.oracle.com/en Function. Create a new `link` (directory entry) for an `existing` file via [Files/createLink](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Files.html#createLink(java.nio.file.Path,java.nio.file.Path)). - + ## `create-sym-link` ``` clojure @@ -245,7 +247,7 @@ Create a symbolic `link` to `target` via [Files/createSymbolicLink](https://docs As of this writing, JDKs do not recognize empty-string `target` `""` as the cwd. Consider instead using a `target` of `"."` to link to the cwd. - + ## `create-temp-dir` ``` clojure @@ -278,7 +280,7 @@ Creates a directory using [Files/createTempDirectory](https://docs.oracle.com/en * `(create-temp-dir {:posix-file-permissions "rwx------"})` * `(create-temp-dir {:dir (path (cwd) "_workdir") :prefix "process-1-"})` - + ## `create-temp-file` ``` clojure @@ -314,7 +316,7 @@ Creates an empty file using [Files/createTempFile](https://docs.oracle.com/en/ja * `(create-temp-file {:posix-file-permissions "rw-------"})` * `(create-temp-file {:dir (path (cwd) "_workdir") :prefix "process-1-" :suffix "-queue"})` - + ## `creation-time` ``` clojure @@ -327,7 +329,7 @@ Function. Returns creation time of `f` as [FileTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/attribute/FileTime.html). See [README notes](/README.md#creation-time) for some details on behaviour. - + ## `cwd` ``` clojure @@ -337,7 +339,7 @@ Returns creation time of `f` as [FileTime](https://docs.oracle.com/en/java/javas Function. Returns current working directory as `Path` - + ## `delete` ``` clojure @@ -349,7 +351,7 @@ Function. Deletes `f` using [Files/delete](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Files.html#delete(java.nio.file.Path)). Returns `nil` if the delete was successful, throws otherwise. Does not follow symlinks. - + ## `delete-if-exists` ``` clojure @@ -361,7 +363,7 @@ Function. Deletes `f` if it exists via [Files/deleteIfExists](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Files.html#deleteIfExists(java.nio.file.Path)). Returns `true` if the delete was successful, `false` if `f` didn't exist. Does not follow symlinks. - + ## `delete-on-exit` ``` clojure @@ -372,7 +374,7 @@ Function. Requests delete of file `f` on exit via [File#deleteOnExit](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/File.html#deleteOnExit()). Returns `f` unaltered. - + ## `delete-tree` ``` clojure @@ -384,7 +386,7 @@ Function. Deletes a file tree `root` using [`walk-file-tree`](#babashka.fs/walk-file-tree). Similar to `rm -rf`. Does not follow symlinks. `force` ensures read-only directories/files are deleted. Similar to `chmod -R +wx` + `rm -rf` - + ## `directory?` ``` clojure @@ -410,7 +412,7 @@ Function. Returns `true` if path `this` ends with path `other` via [Path#endsWith](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Path.html#endsWith(java.nio.file.Path)). See also: [`starts-with?`](#babashka.fs/starts-with?) - + ## `exec-paths` ``` clojure @@ -421,7 +423,7 @@ Function. Returns executable paths (using the `PATH` environment variable). Same as `(split-paths (System/getenv "PATH"))`. - + ## `executable?` ``` clojure @@ -461,7 +463,7 @@ If `f` begins with a tilde (`~`), expand the tilde to the value directory. This is (naively) assumed to be a directory with the same name as the user relative to the parent of the current value of `user.home`. Returns a `Path` - + ## `extension` ``` clojure @@ -471,7 +473,7 @@ If `f` begins with a tilde (`~`), expand the tilde to the value Function. Returns the extension of `path` via [`split-ext`](#babashka.fs/split-ext). - + ## `file` ``` clojure @@ -514,7 +516,7 @@ Function. Converts `ft` [FileTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/attribute/FileTime.html) to an [Instant](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Instant.html). - + ## `file-time->millis` ``` clojure @@ -525,7 +527,7 @@ Function. Converts `ft` [FileTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/attribute/FileTime.html) to epoch millis (long). - + ## `get-attribute` ``` clojure @@ -539,7 +541,7 @@ Return `attribute` for `path` via [Files/getAttribute](https://docs.oracle.com/e Options: * [`:nofollow-links`](/README.md#nofollow-links) - + ## `glob` ``` clojure @@ -584,7 +586,7 @@ Extracts `gz-file` to `dest` directory (default `"."`). Options: * `:replace-existing` - `true` / `false`: overwrite existing files - + ## `gzip` ``` clojure @@ -601,7 +603,7 @@ Gzips `source-file` and writes the output to `dir/out-file`. * `dir` if not provided, the current directory is used. Returns the created gzip file. - + ## `hidden?` ``` clojure @@ -627,7 +629,7 @@ Function. With no arguments, returns the current value of the `user.home` system property as a `Path`. If a `user` is passed, returns that user's home directory as found in the parent of home with no args. - + ## `instant->file-time` ``` clojure @@ -638,7 +640,7 @@ Function. Converts `instant` [Instant](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/Instant.html) to a [FileTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/attribute/FileTime.html). - + ## `last-modified-time` ``` clojure @@ -649,7 +651,7 @@ Converts `instant` [Instant](https://docs.oracle.com/en/java/javase/11/docs/api/ Function. Returns last modified time of `f` as a [FileTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/attribute/FileTime.html). - + ## `list-dir` ``` clojure @@ -672,7 +674,7 @@ Function. Similar to list-dir but accepts multiple roots in `dirs` and returns the concatenated results. - `glob-or-accept` - a glob string such as `"*.edn"` or a `(fn accept [^java.nio.file.Path p]) -> truthy` - + ## `match` ``` clojure @@ -708,7 +710,7 @@ Given a file `root` and match `pattern`, returns matches as vector of Function. Converts epoch millis (long) to a [FileTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/attribute/FileTime.html). - + ## `modified-since` ``` clojure @@ -723,7 +725,7 @@ Returns seq of regular files (non-directories, non-symlinks) from `file-set` tha to compare with. The `file-set` may be a regular file, directory or collection of files (e.g. returned by glob). Directories are searched recursively. - + ## `move` ``` clojure @@ -742,7 +744,7 @@ Move or rename dir or file `source` to `target` dir or file via [Files/move](htt Options: * `replace-existing` - overwrite existing `target`, default `false` * `atomic-move` - watchers will only see complete `target` file, default `false` - + ## `normalize` ``` clojure @@ -777,7 +779,7 @@ Returns the owner of file `f` via [Files/getOwner](https://docs.oracle.com/en/ja Function. Returns parent of `f`. Akin to `dirname` in bash. - + ## `path` ``` clojure @@ -823,7 +825,7 @@ Returns posix file permissions for `f`. Use [`posix->str`](#babashka.fs/posix->s Options: * [`:nofollow-links`](/README.md#nofollow-links) - + ## `read-all-bytes` ``` clojure @@ -833,7 +835,7 @@ Returns posix file permissions for `f`. Use [`posix->str`](#babashka.fs/posix->s Function. Returns contents of file `f` as byte array via [Files/readAllBytes](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Files.html#readAllBytes(java.nio.file.Path)). - + ## `read-all-lines` ``` clojure @@ -844,7 +846,7 @@ Returns contents of file `f` as byte array via [Files/readAllBytes](https://docs Function. Read all lines from a file `f` via [Files/readAllLines](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Files.html#readAllLines(java.nio.file.Path,java.nio.charset.Charset)). - + ## `read-attributes` ``` clojure @@ -856,7 +858,7 @@ Function. Same as [`read-attributes*`](#babashka.fs/read-attributes*) but turns `attributes` for `path` into a map and keywordizes keys. Keywordizing can be changed by passing a `:key-fn` in the `opts` map. - + ## `read-attributes*` ``` clojure @@ -870,7 +872,7 @@ Reads `attributes` for `path` via [Files/readAttributes](https://docs.oracle.com Options: * [`:nofollow-links`](/README.md#nofollow-links) - + ## `read-link` ``` clojure @@ -881,7 +883,7 @@ Function. Reads the target of a symbolic link `path` via [Files/readSymbolicLink](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Files.html#readSymbolicLink(java.nio.file.Path)). The target need not exist. - + ## `readable?` ``` clojure @@ -969,7 +971,7 @@ Returns `root` for `path` as `Path`, or `nil` via [Path#getRoot](https://docs.or Function. Returns `true` if `this` is the same file as `other` via [Files/isSamefile](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Files.html#isSameFile(java.nio.file.Path,java.nio.file.Path)). - + ## `set-attribute` ``` clojure @@ -980,7 +982,7 @@ Returns `true` if `this` is the same file as `other` via [Files/isSamefile](http Function. Set `attribute` for `path` to `value` via [Files/setAttribute](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Files.html#setAttribute(java.nio.file.Path,java.lang.String,java.lang.Object,java.nio.file.LinkOption...)) - + ## `set-creation-time` ``` clojure @@ -996,7 +998,7 @@ Sets creation time of `f` to time (`epoch millis` or [FileTime](https://docs.ora * [`:nofollow-links`](/README.md#nofollow-links) See [README notes](/README.md#set-creation-time) for some details on behaviour. - + ## `set-last-modified-time` ``` clojure @@ -1007,7 +1009,7 @@ Sets creation time of `f` to time (`epoch millis` or [FileTime](https://docs.ora Function. Sets last modified time of `f` to `time` (`epoch millis` or [FileTime](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/attribute/FileTime.html)). - + ## `set-posix-file-permissions` ``` clojure @@ -1017,7 +1019,7 @@ Sets last modified time of `f` to `time` (`epoch millis` or [FileTime](https://d Function. Sets `posix-file-permissions` on `f`. Accepts a string like `"rwx------"` or a set of `PosixFilePermission`. - + ## `size` ``` clojure @@ -1027,7 +1029,7 @@ Sets `posix-file-permissions` on `f`. Accepts a string like `"rwx------"` or a s Function. Returns the size of a file (in bytes). - + ## `split-ext` ``` clojure @@ -1040,7 +1042,7 @@ Function. Splits `path` on extension. If provided, a specific extension `ext`, the extension (without dot), will be used for splitting. Directories are not processed. - + ## `split-paths` ``` clojure @@ -1051,7 +1053,7 @@ Function. Splits a `joined-paths` list given as a string joined by the OS-specific [`path-separator`](#babashka.fs/path-separator) into a vec of paths. On UNIX systems, the separator is ':', on Microsoft Windows systems it is ';'. - + ## `starts-with?` ``` clojure @@ -1063,7 +1065,7 @@ Function. Returns `true` if path `this` starts with path `other` via [Path#startsWith](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Path.html#startsWith(java.nio.file.Path)). See also: [`ends-with?`](#babashka.fs/ends-with?) - + ## `str->posix` ``` clojure @@ -1086,7 +1088,7 @@ Converts a string `s` to a set of `PosixFilePermission`. Function. Strips extension for `path` via [`split-ext`](#babashka.fs/split-ext). - + ## `sym-link?` ``` clojure @@ -1096,7 +1098,7 @@ Strips extension for `path` via [`split-ext`](#babashka.fs/split-ext). Function. Determines if `f` is a symbolic link via [Files/isSymbolicLink](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Files.html#isSymbolicLink(java.nio.file.Path)). - + ## `temp-dir` ``` clojure @@ -1106,7 +1108,7 @@ Determines if `f` is a symbolic link via [Files/isSymbolicLink](https://docs.ora Function. Returns `java.io.tmpdir` property as path. - + ## `unixify` ``` clojure @@ -1116,7 +1118,7 @@ Returns `java.io.tmpdir` property as path. Function. Returns path as string with Unix-style file separators (`/`). - + ## `unzip` ``` clojure @@ -1138,7 +1140,7 @@ Unzips `zip-file` to `dest` directory (default `"."`). * `:name` and the name of the ZipEntry (result of calling `getName`) Extraction only occurs if a truthy value is returned (i.e. not nil/false). - + ## `update-file` ``` clojure @@ -1153,7 +1155,7 @@ Updates the contents of text file `path` using `f` applied to old contents and ` Options: * `:charset` - charset of file, default to "utf-8" - + ## `walk-file-tree` ``` clojure @@ -1198,7 +1200,7 @@ Returns `Path` to first executable `program` found in `:paths` `opt`, similar to When `program` is a relative or absolute path, `:paths` option is not consulted. On Windows, the `:win-exts` variants are still searched. On other OSes, the path for `program` will be returned if executable, else `nil`. - + ## `which-all` ``` clojure @@ -1209,7 +1211,7 @@ Returns `Path` to first executable `program` found in `:paths` `opt`, similar to Function. Returns every `Path` to `program` found in ([`exec-paths`](#babashka.fs/exec-paths)). See [`which`](#babashka.fs/which). - + ## `windows?` ``` clojure @@ -1219,7 +1221,7 @@ Returns every `Path` to `program` found in ([`exec-paths`](#babashka.fs/exec-pat Function. Returns true if OS is Windows. - + ## `with-temp-dir` ``` clojure @@ -1246,7 +1248,7 @@ Evaluates body with binding-name bound to the result of `(create-temp-dir ;; d no longer exists here ``` - + ## `writable?` ``` clojure @@ -1281,7 +1283,7 @@ Writes `bytes` to `path` via [Files/write](https://docs.oracle.com/en/java/javas (fs/write-bytes f (.getBytes (String. "foo"))) ;; overwrites + truncates or creates new file (fs/write-bytes f (.getBytes (String. "foo")) {:append true}) ``` - + ## `write-lines` ``` clojure @@ -1302,7 +1304,7 @@ Writes `lines`, a seqable of strings to `path` via [Files/write](https://docs.or * `:write` (default `true`) * `:append` (default `false`) * or any `java.nio.file.StandardOption`. - + ## `xdg-cache-home` ``` clojure @@ -1316,7 +1318,7 @@ Path representing the base directory relative to which user-specific non-essenti Returns path based on the value of env-var `XDG_CACHE_HOME` (if set and representing an absolute path), else `(fs/path (fs/home) ".cache")`. When provided, appends `app` to the path. - + ## `xdg-config-home` ``` clojure @@ -1330,7 +1332,7 @@ Path representing the base directory relative to which user-specific configurati Returns path based on the value of env-var `XDG_CONFIG_HOME` (if set and representing an absolute path), else `(fs/path (fs/home) ".config")`. When provided, appends `app` to the path. - + ## `xdg-data-home` ``` clojure @@ -1344,7 +1346,7 @@ Path representing the base directory relative to which user-specific data files Returns path based on the value of env-var `XDG_DATA_HOME` (if set and representing an absolute path), else `(fs/path (fs/home) ".local" "share")`. When provided, appends `app` to the path. - + ## `xdg-state-home` ``` clojure @@ -1358,7 +1360,7 @@ Path representing the base directory relative to which user-specific state files Returns path based on the value of env-var `XDG_STATE_HOME` (if set and representing an absolute path), else `(fs/path (fs/home) ".local" "state")`. When provided, appends `app` to the path. - + ## `zip` ``` clojure @@ -1376,4 +1378,4 @@ Zips entry or `entries` into `zip-file`. An entry may be a file or * `:root`: directory which will be elided in zip. E.g.: `(fs/zip ["src"] {:root "src"})` * `:path-fn`: a single-arg function from file system path to zip entry path. - + diff --git a/CHANGELOG.md b/CHANGELOG.md index 8182341..fdd6cd9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Babashka [fs](https://github.com/babashka/fs): file system utility library for C - [#154](https://github.com/babashka/fs/issues/154) reflect in directory check and docs that `move` never follows symbolic links ([@lread](https://github.com/lread)) - [#181](https://github.com/babashka/fs/issues/181) `delete-tree` now deletes broken symbolic link `root` ([@lread](https://github.com/lread)) +- [#193](https://github.com/babashka/fs/issues/193) `create-dirs` now recognizes sym-linked dirs on JDK 11 ([@lread](https://github.com/lread)) ## 0.5.30 (2025-11-25) diff --git a/src/babashka/fs.cljc b/src/babashka/fs.cljc index 336a472..2ba861a 100644 --- a/src/babashka/fs.cljc +++ b/src/babashka/fs.cljc @@ -503,6 +503,8 @@ "Creates dir using [Files/createDirectory](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/nio/file/Files.html#createDirectory(java.nio.file.Path,java.nio.file.attribute.FileAttribute...)). Does not create parents. + Returns created directory as `Path`. + Options: * `:posix-file-permissions` permission for unix-like systems" ([path] @@ -520,7 +522,10 @@ * `:posix-file-permissions` permission for unix-like systems" ([path] (create-dirs path nil)) ([path {:keys [:posix-file-permissions]}] - (Files/createDirectories (as-path path) (posix->attrs posix-file-permissions)))) + (let [p (as-path path)] + (if (directory? p) ;; compensate for JDK11 which does not follow symlinks in its createDirectories + p + (Files/createDirectories (as-path path) (posix->attrs posix-file-permissions)))))) (defn set-posix-file-permissions "Sets `posix-file-permissions` on `f`. Accepts a string like `\"rwx------\"` or a set of `PosixFilePermission`." diff --git a/test/babashka/fs_cwd_test.clj b/test/babashka/fs_cwd_test.clj index 6c8b45a..fc87680 100644 --- a/test/babashka/fs_cwd_test.clj +++ b/test/babashka/fs_cwd_test.clj @@ -509,6 +509,50 @@ ;; sl- symbolic link tests (when-not (fs/windows?) + ;; + ;; create-dirs + ;; + (deftest sl-create-dirs-test + (fs/create-dirs "dir1/dir2/dir3") + (fs/create-sym-link "link-dir1" "dir1") + (fs/create-sym-link "dir1/link-dir2" "dir2") + (spit "dir1/file1.txt" "file1") + (spit "dir1/dir2/file2.txt" "file2") + (fs/create-sym-link "dir1/link-file1.txt" "file1.txt") + (fs/create-sym-link "dir1/dir2/link-file2.txt" "file2.txt") + (let [before (fsnapshot)] + ;; no-ops, dirs exist + (doseq [p ["link-dir1" + "dir1/link-dir2" + "link-dir1/link-dir2" + "link-dir1/dir2"]] + (is (= (fs/path p) (fs/create-dirs p)) + (format "creating existing path %s does not throw" p))) + + ;; failures + (doseq [p ["link-dir1/file1.txt" + "link-dir1/link-dir2/file2.txt" + "link-dir1/link-file1.txt" + "link-dir1/link-dir2/link-file2.txt"]] + (is (thrown? java.nio.file.FileAlreadyExistsException (fs/create-dirs p)) + (format "create over existinf file %s throws" p))) + + (is (match? before (fsnapshot)) + "no changes expected for no-ops and throws")) + + ;; creates dirs with symlinks in parent path + (doseq [[create-path expected-new-path] + [["link-dir1/new1" "dir1/new1"] + ["dir1/link-dir2/new2" "dir1/dir2/new2"] + ["link-dir1/link-dir2/new3" "dir1/dir2/new3"] + ["link-dir1/dir2/new4" "dir1/dir2/new4"]]] + (is (= (fs/path create-path) (fs/create-dirs create-path)) + "creates new dir when parent path has sym-links to dirs") + (is (= true (fs/exists? expected-new-path)) + (format "new %s item exists" expected-new-path)) + (is (= true (fs/directory? expected-new-path {:nofollow-links true})) + (format "new %s is directory" expected-new-path)))) + ;; ;; delete-tree ;; @@ -561,7 +605,7 @@ (is (= false (fs/exists? "good-link1" {:nofollow-links true}))) (is (= true (fs/directory? "good-link2"))) ;; via link follow (is (= (fs/path "dir1") (fs/read-link "good-link2")))) - + (deftest sl-move-good-link-under-dir-test (fs/create-dir "dir1") (fs/create-dir "dir2") @@ -599,7 +643,7 @@ (fs/create-dir "dir1") (fs/create-sym-link "good-link1" "dir1") - (fs/move "good-link1" "good-link2" ) + (fs/move "good-link1" "good-link2") (is (= false (fs/exists? "good-link1" {:nofollow-links true}))) (is (= true (fs/exists? "good-link2" {:nofollow-links true}))) (is (= true (fs/sym-link? "good-link2"))) diff --git a/test/babashka/fs_test.clj b/test/babashka/fs_test.clj index ab43dfc..b0c8a48 100644 --- a/test/babashka/fs_test.clj +++ b/test/babashka/fs_test.clj @@ -157,6 +157,15 @@ (is (= (slurp (fs/file tmp-dir "hard-link.txt")) (slurp (fs/file tmp-dir "dudette.txt"))))))) +(deftest directory?-test + (let [tmp-dir (temp-dir) + tmp-file (fs/path tmp-dir "tmp.txt")] + (spit (fs/file tmp-file) "foo") + (is (= true (fs/directory? tmp-dir))) + (is (= false (fs/directory? tmp-file))) + (is (= false (fs/directory? "idontexist"))) + (is (= false (fs/directory? (fs/path tmp-dir "idontexist")))))) + (deftest regular-file?-test (let [tmp-dir (temp-dir) tmp-file (fs/path tmp-dir "tmp.txt")]