std.fs
filesystem paths, archives, attributes, walking, and watching
std.fs is a higher-level standard library family in foundation-base. This page explains when to use it, how it fits internally, and where to find the API surface.
1 Motivation
Use this layer when application or tooling code needs the behavior described by the page title without reaching directly into implementation namespaces. The top-level namespace is the starting point; subnamespaces expose more focused building blocks.
2 How to use it
Require the top-level namespace for common workflows, then move to subnamespaces when you need a lower-level primitive. Existing tests under test/std/fs and test/std/fs_test.clj are the best executable examples for edge cases.
convert between namespace and file paths
(ns->file 'std.fs-test)
=> "std/fs_test"
(file->ns "std/fs_test")
=> "std.fs-test"
read a file's namespace
(file-namespace "src/std/fs.clj")
=> 'std.fs
create and query a path
(exists? (path "project.clj"))
=> true
(file-name (path "project.clj"))
=> "project.clj"
3 Internal usage
This library family is used across source, tests, generated examples, and docs tooling. During detailed documentation passes, collect concrete usage with code.manage/find-usages and code.manage/locate-code, then keep only high-signal examples in the page narrative.
4 API
- +empty-string-array+
- attributes
- copy
- copy-into
- copy-single
- create-directory
- create-symlink
- create-tmpdir
- create-tmpfile
- delete
- directory?
- empty-directory?
- executable?
- exists?
- file
- file->ns
- file-name
- file-namespace
- file-suffix
- file-system
- file?
- get-namespace
- hidden?
- input-stream
- last-modified
- link?
- list
- move
- normalise
- ns->file
- nth-segment
- option
- output-stream
- parent
- path
- path-functionality
- path?
- permissions
- read-all-bytes
- read-all-lines
- read-code
- readable?
- relativize
- root
- section
- segment-count
- select
- set-attributes
- set-executable
- subpath
- to-file
- typestring
- writable?
- write-all-bytes
- write-into
v 3.0
(defn attributes
([path]
(-> (path/path path)
(Files/readAttributes (str (name common/*system*) ":*")
^"[Ljava.nio.file.LinkOption;" common/*no-follow*)
(attrs->map))))
link
(attributes "project.clj") ;; {:owner "chris", ;; :group "staff", ;; :permissions "rw-r--r--", ;; :file-key "(dev=1000004,ino=2351455)", ;; :ino 2351455, ;; :is-regular-file true. ;; :is-directory false, :uid 501, ;; :is-other false, :mode 33188, :size 4342, ;; :gid 20, :ctime 1476755481000, ;; :nlink 1, ;; :last-access-time 1476755481000, ;; :is-symbolic-link false, ;; :last-modified-time 1476755481000, ;; :creation-time 1472282953000, ;; :dev 16777220, :rdev 0} => map?
copy ^
[source target] [source target opts]
copies all specified files from one to another
v 3.0
(defn copy
([source target]
(copy source target {}))
([source target opts]
(let [copy-fn (fn [{:keys [root path attrs target accumulator simulate]}]
(let [rel (.relativize ^Path root path)
dest (.resolve ^Path target rel)
^"[Ljava.nio.file.CopyOption;" copts (->> [:copy-attributes :nofollow-links]
(or (:options opts))
(mapv common/option)
(into-array CopyOption))]
(when-not simulate
(Files/createDirectories (.getParent dest) attr/*empty*)
(when (not (path/directory? dest))
(Files/copy ^Path path ^Path dest copts)))
(swap! accumulator
assoc
(str path)
(str dest))))]
(walk/walk source
(merge {:target (path/path target)
:directory copy-fn
:file copy-fn
:with #{:root}
:accumulator (atom {})
:accumulate #{}}
opts)))))
link
(copy "src" ".src" {:include [".clj"]}) => map? (delete ".src")
copy-into ^
[source target] [source target opts]
copies a single file to a destination
v 3.0
(defn copy-into
([source target]
(copy-into source target {:include [path/file?]}))
([source target opts]
(let [source-root (path/path source)
target-root (path/path target)
files (select source-root (select-keys opts [:include]))]
(mapv (fn [source]
(let [path (path/relativize source-root source)
target (path/path target-root path)]
(copy-single source target (select-keys opts [:options]))))
files))))
link
(create-directory "test-scratch") (copy-into "src/std/fs/api.clj" "test-scratch/fs/api.clj") => vector? (delete "test-scratch")
copy-single ^
[source target] [source target opts]
copies a single file to a destination
v 3.0
(defn copy-single
([source target]
(copy-single source target {}))
([source target opts]
(if-let [dir (path/parent target)]
(if-not (path/exists? dir)
(create-directory dir)))
(let [^"[Ljava.nio.file.CopyOption;" opts (->> (:options opts)
(mapv common/option)
(into-array CopyOption))]
(Files/copy (path/path source)
(path/path target)
opts))))
link
(copy-single "project.clj" "test-scratch/project.clj.bak" {:options #{:replace-existing}}) => (path/path "." "test-scratch/project.clj.bak") (delete "test-scratch/project.clj.bak")
v 3.0
(defn create-directory
([path]
(create-directory path {}))
([path attrs]
(Files/createDirectories (path/path path)
(attr/map->attr-array attrs))))
link
(do (create-directory "test-scratch/.hello/.world/.foo") (path/directory? "test-scratch/.hello/.world/.foo")) => true (delete "test-scratch/.hello")
create-symlink ^
[path link-to] [path link-to attrs]
creates a symlink to another file
v 3.0
(defn create-symlink
([path link-to]
(create-symlink path link-to {}))
([path link-to attrs]
(Files/createSymbolicLink (path/path path)
(path/path link-to)
(attr/map->attr-array attrs))))
link
(do (create-symlink "test-scratch/project.lnk" "project.clj") (path/link? "test-scratch/project.lnk")) => true (delete "test-scratch/project.lnk")
v 3.0
(defn create-tmpdir
([]
(create-tmpdir ""))
([prefix]
(Files/createTempDirectory prefix (make-array FileAttribute 0))))
link
(create-tmpdir) => java.nio.file.Path
v 3.0
(defn create-tmpfile
([]
(java.io.File/createTempFile "tmp" ""))
([contents]
(let [f (create-tmpfile)]
(spit f contents)
f)))
link
(create-tmpfile) ;;#file:"/var/folders/rc/4nxjl26j50gffnkgm65ll8gr0000gp/T/tmp2270822955686495575" => java.io.File
v 3.0
(defn delete
([root] (delete root {}))
([root opts]
(let [delete-fn (fn [{:keys [path attrs accumulator simulate]}]
(try (if-not simulate
(Files/delete path))
(swap! accumulator conj (str path))
(catch DirectoryNotEmptyException e)))]
(walk/walk root
(merge {:directory {:post delete-fn}
:file delete-fn
:with #{:root}
:accumulator (atom #{})
:accumulate #{}}
opts)))))
link
(do (create-directory "test-scratch") (copy-single "src/std/fs/api.clj" "test-scratch/fs/api.clj") (delete "test-scratch" {:include ["api.clj"]})) => set? (delete "test-scratch") => set?
v 3.0
(defn directory?
([path]
(Files/isDirectory (path/path path) common/*no-follow*)))
link
(directory? "src") => true (directory? "project.clj") => false
empty-directory? ^
[path]
checks if a directory is empty, returns true if both are true
v 3.0
(defn empty-directory?
([path]
(let [path (path/path path)]
(if (directory? path)
(zero? (.count (Files/list path)))
(throw (Exception. (str "Not a directory: " path)))))))
link
(empty-directory? ".") => false
v 3.0
(defn executable?
([path]
(Files/isExecutable (path/path path))))
link
(executable? "project.clj") => boolean? (executable? "/usr/bin/whoami") => true
v 3.0
(defn exists?
([path]
(Files/exists (path/path path) common/*no-follow*)))
link
(exists? "project.clj") => true (exists? "NON.EXISTENT") => false
v 3.0
(defn ^File file
([path]
(path/to-file (path/path path))))
link
(file "project.clj") => java.io.File
v 3.0
(defn file->ns
([^String file]
(-> file
(.replaceAll "/" ".")
(.replaceAll "_" "-"))))
link
(file->ns "std/fs_test") => "std.fs-test"
v 3.0
(defn file-namespace
([path]
(try
(read-code path get-namespace)
(catch Throwable t
(env/local :println path "Cannot be loaded")))))
link
(file-namespace "src/std/fs.clj") => 'std.fs
v 3.0
(defn file-suffix
([file]
(-> (str file)
(clojure.string/split #".")
last
keyword)))
link
(file-suffix "hello.clj") => :clj (file-suffix "hello.java") => :java
v 3.0
(defn file-system
([x]
(.getFileSystem (path x))))
link
(file-system ".") ;; #object[sun.nio.fs.MacOSXFileSystem 0x512a9870 "sun.nio.fs.MacOSXFileSystem@512a9870"] => java.nio.file.FileSystem
v 3.0
(defn file?
([path]
(Files/isRegularFile (path/path path) common/*no-follow*)))
link
(file? "project.clj") => true (file? "src") => false
v 4.0
(defn get-namespace
([forms]
(->> forms
(filter #(-> % first (= 'ns)))
first
second)))
link
(get-namespace '[(ns hello)]) => 'hello
v 3.0
(defn hidden?
([path]
(Files/isHidden (path/path path))))
link
(hidden? ".gitignore") => true (hidden? "project.clj") => false
v 3.0
(defn ^InputStream input-stream
([path]
(input-stream path {}))
([path opts]
(Files/newInputStream (path/path path)
(->> (:options opts)
(mapv common/option)
(into-array OpenOption)))))
link
(input-stream "project.clj") => (partial instance? InputStream)
v 3.0
(defn last-modified
([path]
(.lastModified (path/file path))))
link
(last-modified "project.clj") => integer?
v 3.0
(defn link?
([path]
(Files/isSymbolicLink (path/path path))))
link
(link? "project.clj") => false (link? (api/create-symlink "project.lnk" "project.clj")) => true (api/delete "project.lnk")
v 3.0
(defn list
([root] (list root {}))
([root opts]
(let [gather-fn (fn [{:keys [path attrs accumulator]}]
(swap! accumulator
assoc
(str path)
(str (path/permissions path) "/" (path/typestring path))))]
(walk/walk root
(merge {:depth 1
:directory gather-fn
:file gather-fn
:accumulator (atom {})
:accumulate #{}
:with #{}}
opts)))))
link
(keys (list "src/std/fs")) => (contains [(str (path/path "src/std/fs/api.clj")) (str (path/path "src/std/fs/path.clj"))] :in-any-order :gaps-ok)
v 3.0
(defn move
([source target]
(move source target {}))
([source target opts]
(let [move-fn (fn [{:keys [root path attrs target accumulator simulate]}]
(let [rel (.relativize ^Path root path)
dest (.resolve ^Path target rel)
^"[Ljava.nio.file.CopyOption;" copts (->> [:atomic-move]
(or (:options opts))
(mapv common/option)
(into-array CopyOption))]
(when-not simulate
(Files/createDirectories (.getParent dest) attr/*empty*)
(Files/move ^Path path ^Path dest copts))
(swap! accumulator
assoc
(str path)
(str dest))))
results (walk/walk source
(merge {:target (path/path target)
:recursive true
:directory {:post (fn [{:keys [path]}]
(if (path/empty-directory? path)
(delete path opts)))}
:file move-fn
:with #{:root}
:accumulator (atom {})
:accumulate #{}}
opts))]
results)))
link
(create-directory "test-scratch/move-src") (spit "test-scratch/move-src/hello" "world") (move "test-scratch/move-src" "test-scratch/move-dest") => map? (path/exists? "test-scratch/move-dest/hello") => true (path/exists? "test-scratch/move-src") => false (delete "test-scratch/move-dest")
v 3.0
(defn normalise
([^String s]
(cond (= common/*os* :windows)
(cond (not (.startsWith s common/*sep*))
(if (= 1 (.indexOf s ":\"))
s
(str common/*cwd* common/*sep* s))
:else s)
:else
(cond (= s "~")
common/*home*
(.startsWith s (str "~" common/*sep*))
(.replace s "~" ^String common/*home*)
(not (.startsWith s common/*sep*))
(str common/*cwd* common/*sep* s)
:else s))))
link
(normalise ".") => (str common/*cwd* "/" ".") (normalise "~/hello/world.txt") => (str common/*home* "/hello/world.txt") (normalise "/usr/home") => "/usr/home"
v 3.0
(defn ^String ns->file
([ns]
(-> (str ns)
(.replaceAll "\." "/")
(.replaceAll "-" "_"))))
link
(ns->file 'std.fs-test) => "std/fs_test"
v 3.0
(defn nth-segment
([x i]
(.getName (path x) i)))
link
(str (nth-segment "/usr/local/bin" 1)) => "local"
v 3.0
(defn option
([] (keys +all-options+))
([k]
(+all-options+ k)))
link
(option) => (contains [:atomic-move :create-new :skip-siblings :read :continue :create :terminate :copy-attributes :append :truncate-existing :sync :follow-links :delete-on-close :write :dsync :replace-existing :sparse :nofollow-links :skip-subtree]) (option :read) => java.nio.file.StandardOpenOption/READ
v 3.0
(defn output-stream
([path]
(output-stream path {}))
([path opts]
(Files/newOutputStream (path/path path)
(->> (:options opts)
(mapv common/option)
(into-array OpenOption)))))
link
(let [tmp (api/create-tmpfile)] (output-stream tmp) => (partial instance? OutputStream))
v 3.0
(defn ^Path path
([x]
(cond (instance? Path x)
x
(string? x)
(.normalize (Paths/get (normalise x) +empty-string-array+))
(vector? x)
(apply path x)
(instance? java.net.URI x)
(Paths/get x)
(instance? java.net.URL x)
(path (.getFile ^java.net.URL x))
(instance? File x)
(path (.toString ^File x))
:else
(throw (Exception. (format "Input %s is not of the correct format" x)))))
([s & more]
(.normalize (Paths/get (normalise (str s)) (into-array String (map str more))))))
link
(path "project.clj") => (partial instance? java.nio.file.Path) (path (path "project.clj")) ;; idempotent => (partial instance? java.nio.file.Path) (path "~") ;; tilda => (partial instance? java.nio.file.Path) (path "src" "std/fs.clj") ;; multiple arguments => (partial instance? java.nio.file.Path) (path ["src" "std" "fs.clj"]) ;; vector => (partial instance? java.nio.file.Path) (path (java.io.File. ;; java.io.File object "src/std/fs.clj")) => (partial instance? java.nio.file.Path) (path (java.net.URI. ;; java.net.URI object "file:///tmp/project.clj")) => (partial instance? java.nio.file.Path)
link
(str (path "~")) => common/*home* (str (path "~/../shared/data")) => (str (->> (re-pattern common/*sep*) (clojure.string/split common/*home*) (butlast) (clojure.string/join "/")) "/shared/data") (str (path ["hello" "world.txt"])) => (str common/*cwd* "/hello/world.txt")
v 3.0
(defn permissions
([path]
(-> (path/path path)
(Files/getPosixFilePermissions common/*no-follow*)
(PosixFilePermissions/toString))))
link
(permissions "src") => string?
v 3.0
(defn read-all-bytes
([path]
(Files/readAllBytes (path/path path))))
link
(read-all-bytes "project.clj") => (partial instance? (class (byte-array 0)))
v 3.0
(defn read-all-lines
([path]
(Files/readAllLines (path/path path))))
link
(read-all-lines "project.clj") => (partial every? string?)
v 3.0
(defn read-code
([path]
(read-code path identity))
([path f]
(with-open [reader (->> (path/input-stream (path/path path))
(InputStreamReader.)
(PushbackReader.))]
(->> (repeatedly #(try (clojure.core/read reader)
(catch Throwable e)))
(take-while identity)
f
(collection/unlazy)))))
link
(->> (read-code "src/std/fs.clj") first (take 2)) => '(ns std.fs)
v 3.0
(defn readable?
([path]
(Files/isReadable (path/path path))))
link
(readable? "project.clj") => true
v 3.0
(defn relativize
([x other]
(.relativize (path x) (path other))))
link
(str (relativize "test" "src/std")) => "../src/std"
v 3.0
(defn section
([s & more]
(Paths/get s (into-array String more))))
link
(str (section "project.clj")) => "project.clj" (str (section "src" "std/fs.clj")) => "src/std/fs.clj"
v 3.0
(defn select
([root]
(select root nil))
([root opts]
(walk/walk root opts)))
link
(->> (select "src/std/fs") (map #(path/relativize "src/std/fs" %)) (map str) (sort)) => (contains ["api.clj" "archive.clj" "attribute.clj" "common.clj" "interop.clj" "path.clj" "walk.clj" "watch.clj"] :in-any-order)
v 3.0
(defn set-attributes
([path m]
(reduce-kv (fn [_ k v]
(-> (path/path path)
(Files/setAttribute (str (name common/*system*) ":"
((std.string.wrap/wrap case/camel-case) (name k)))
(attr-value k v)
common/*no-follow*)))
nil
m)))
link
(do (set-attributes "project.clj" {:last-modified-time 0}) (-> (attributes "project.clj") :last-modified-time)) => 0
v 3.0
(defn set-executable
[path]
(let [perms (doto (Files/getPosixFilePermissions path (make-array LinkOption 0))
(.add PosixFilePermission/OWNER_EXECUTE)
(.add PosixFilePermission/GROUP_EXECUTE)
(.add PosixFilePermission/OTHERS_EXECUTE))]
(Files/setPosixFilePermissions path perms)))
link
(let [tmp (api/create-tmpfile)] (set-executable (path tmp)) (executable? tmp) => true)
v 3.0
(defn subpath
([x start end]
(.subpath (path x) start end)))
link
(str (subpath "/usr/local/bin/hello" 1 3)) => "local/bin"
v 3.0
(defn to-file
([^Path path]
(.toFile path)))
link
(to-file (section "project.clj")) => (all java.io.File #(-> % str (= "project.clj")))
v 3.0
(defn typestring
([path]
(let [path (path/path path)]
(cond (Files/isDirectory path (LinkOption/values))
"d"
(Files/isSymbolicLink path)
"l"
:else "-"))))
link
(typestring "src") => "d" (typestring "project.clj") => "-"
v 3.0
(defn writable?
([path]
(Files/isWritable (path/path path))))
link
(writable? "project.clj") => true
v 3.0
(defn write-all-bytes
([path bytes]
(write-all-bytes path bytes {}))
([path ^bytes bytes opts]
(let [^"[Ljava.nio.file.OpenOption;" opts (->> (:options opts)
(mapv common/option)
(into-array OpenOption))]
(Files/write ^Path (path/path path)
bytes
opts))))
link
(let [tmp (api/create-tmpfile)] (write-all-bytes tmp (.getBytes "Hello World")) (first (read-all-lines tmp)) => "Hello World")
v 3.0
(defn write-into
([path ^InputStream stream]
(write-into path stream {}))
([path ^InputStream stream opts]
(let [^"[Ljava.nio.file.CopyOption;" opts (->> (:options opts)
(mapv common/option)
(into-array CopyOption))]
(Files/copy stream (path/path path) opts))))
link
(let [tmp (api/create-tmpfile)] (write-into tmp (java.io.FileInputStream. "project.clj") {:options #{:replace-existing}}) (vec (read-all-bytes tmp)) => (vec (read-all-bytes "project.clj")))
- +empty-string-array+
- directory?
- empty-directory?
- executable?
- exists?
- file
- file-name
- file-suffix
- file-system
- file?
- hidden?
- input-stream
- last-modified
- link?
- normalise
- nth-segment
- output-stream
- parent
- path
- path-functionality
- path?
- permissions
- read-all-bytes
- read-all-lines
- readable?
- relativize
- root
- section
- segment-count
- set-executable
- subpath
- to-file
- typestring
- writable?
- write-all-bytes
- write-into
v 3.0
(defn directory?
([path]
(Files/isDirectory (path/path path) common/*no-follow*)))
link
(directory? "src") => true (directory? "project.clj") => false
empty-directory? ^
[path]
checks if a directory is empty, returns true if both are true
v 3.0
(defn empty-directory?
([path]
(let [path (path/path path)]
(if (directory? path)
(zero? (.count (Files/list path)))
(throw (Exception. (str "Not a directory: " path)))))))
link
(empty-directory? ".") => false
v 3.0
(defn executable?
([path]
(Files/isExecutable (path/path path))))
link
(executable? "project.clj") => boolean? (executable? "/usr/bin/whoami") => true
v 3.0
(defn exists?
([path]
(Files/exists (path/path path) common/*no-follow*)))
link
(exists? "project.clj") => true (exists? "NON.EXISTENT") => false
v 3.0
(defn ^File file
([path]
(path/to-file (path/path path))))
link
(file "project.clj") => java.io.File
v 3.0
(defn file-suffix
([file]
(-> (str file)
(clojure.string/split #".")
last
keyword)))
link
(file-suffix "hello.clj") => :clj (file-suffix "hello.java") => :java
v 3.0
(defn file-system
([x]
(.getFileSystem (path x))))
link
(file-system ".") ;; #object[sun.nio.fs.MacOSXFileSystem 0x512a9870 "sun.nio.fs.MacOSXFileSystem@512a9870"] => java.nio.file.FileSystem
v 3.0
(defn file?
([path]
(Files/isRegularFile (path/path path) common/*no-follow*)))
link
(file? "project.clj") => true (file? "src") => false
v 3.0
(defn hidden?
([path]
(Files/isHidden (path/path path))))
link
(hidden? ".gitignore") => true (hidden? "project.clj") => false
v 3.0
(defn ^InputStream input-stream
([path]
(input-stream path {}))
([path opts]
(Files/newInputStream (path/path path)
(->> (:options opts)
(mapv common/option)
(into-array OpenOption)))))
link
(input-stream "project.clj") => (partial instance? InputStream)
v 3.0
(defn last-modified
([path]
(.lastModified (path/file path))))
link
(last-modified "project.clj") => integer?
v 3.0
(defn link?
([path]
(Files/isSymbolicLink (path/path path))))
link
(link? "project.clj") => false (link? (api/create-symlink "project.lnk" "project.clj")) => true (api/delete "project.lnk")
v 3.0
(defn normalise
([^String s]
(cond (= common/*os* :windows)
(cond (not (.startsWith s common/*sep*))
(if (= 1 (.indexOf s ":\"))
s
(str common/*cwd* common/*sep* s))
:else s)
:else
(cond (= s "~")
common/*home*
(.startsWith s (str "~" common/*sep*))
(.replace s "~" ^String common/*home*)
(not (.startsWith s common/*sep*))
(str common/*cwd* common/*sep* s)
:else s))))
link
(normalise ".") => (str common/*cwd* "/" ".") (normalise "~/hello/world.txt") => (str common/*home* "/hello/world.txt") (normalise "/usr/home") => "/usr/home"
v 3.0
(defn nth-segment
([x i]
(.getName (path x) i)))
link
(str (nth-segment "/usr/local/bin" 1)) => "local"
v 3.0
(defn output-stream
([path]
(output-stream path {}))
([path opts]
(Files/newOutputStream (path/path path)
(->> (:options opts)
(mapv common/option)
(into-array OpenOption)))))
link
(let [tmp (api/create-tmpfile)] (output-stream tmp) => (partial instance? OutputStream))
v 3.0
(defn ^Path path
([x]
(cond (instance? Path x)
x
(string? x)
(.normalize (Paths/get (normalise x) +empty-string-array+))
(vector? x)
(apply path x)
(instance? java.net.URI x)
(Paths/get x)
(instance? java.net.URL x)
(path (.getFile ^java.net.URL x))
(instance? File x)
(path (.toString ^File x))
:else
(throw (Exception. (format "Input %s is not of the correct format" x)))))
([s & more]
(.normalize (Paths/get (normalise (str s)) (into-array String (map str more))))))
link
(path "project.clj") => (partial instance? java.nio.file.Path) (path (path "project.clj")) ;; idempotent => (partial instance? java.nio.file.Path) (path "~") ;; tilda => (partial instance? java.nio.file.Path) (path "src" "std/fs.clj") ;; multiple arguments => (partial instance? java.nio.file.Path) (path ["src" "std" "fs.clj"]) ;; vector => (partial instance? java.nio.file.Path) (path (java.io.File. ;; java.io.File object "src/std/fs.clj")) => (partial instance? java.nio.file.Path) (path (java.net.URI. ;; java.net.URI object "file:///tmp/project.clj")) => (partial instance? java.nio.file.Path)
link
(str (path "~")) => common/*home* (str (path "~/../shared/data")) => (str (->> (re-pattern common/*sep*) (clojure.string/split common/*home*) (butlast) (clojure.string/join "/")) "/shared/data") (str (path ["hello" "world.txt"])) => (str common/*cwd* "/hello/world.txt")
v 3.0
(defn permissions
([path]
(-> (path/path path)
(Files/getPosixFilePermissions common/*no-follow*)
(PosixFilePermissions/toString))))
link
(permissions "src") => string?
v 3.0
(defn read-all-bytes
([path]
(Files/readAllBytes (path/path path))))
link
(read-all-bytes "project.clj") => (partial instance? (class (byte-array 0)))
v 3.0
(defn read-all-lines
([path]
(Files/readAllLines (path/path path))))
link
(read-all-lines "project.clj") => (partial every? string?)
v 3.0
(defn readable?
([path]
(Files/isReadable (path/path path))))
link
(readable? "project.clj") => true
v 3.0
(defn relativize
([x other]
(.relativize (path x) (path other))))
link
(str (relativize "test" "src/std")) => "../src/std"
v 3.0
(defn section
([s & more]
(Paths/get s (into-array String more))))
link
(str (section "project.clj")) => "project.clj" (str (section "src" "std/fs.clj")) => "src/std/fs.clj"
v 3.0
(defn set-executable
[path]
(let [perms (doto (Files/getPosixFilePermissions path (make-array LinkOption 0))
(.add PosixFilePermission/OWNER_EXECUTE)
(.add PosixFilePermission/GROUP_EXECUTE)
(.add PosixFilePermission/OTHERS_EXECUTE))]
(Files/setPosixFilePermissions path perms)))
link
(let [tmp (api/create-tmpfile)] (set-executable (path tmp)) (executable? tmp) => true)
v 3.0
(defn subpath
([x start end]
(.subpath (path x) start end)))
link
(str (subpath "/usr/local/bin/hello" 1 3)) => "local/bin"
v 3.0
(defn to-file
([^Path path]
(.toFile path)))
link
(to-file (section "project.clj")) => (all java.io.File #(-> % str (= "project.clj")))
v 3.0
(defn typestring
([path]
(let [path (path/path path)]
(cond (Files/isDirectory path (LinkOption/values))
"d"
(Files/isSymbolicLink path)
"l"
:else "-"))))
link
(typestring "src") => "d" (typestring "project.clj") => "-"
v 3.0
(defn writable?
([path]
(Files/isWritable (path/path path))))
link
(writable? "project.clj") => true
v 3.0
(defn write-all-bytes
([path bytes]
(write-all-bytes path bytes {}))
([path ^bytes bytes opts]
(let [^"[Ljava.nio.file.OpenOption;" opts (->> (:options opts)
(mapv common/option)
(into-array OpenOption))]
(Files/write ^Path (path/path path)
bytes
opts))))
link
(let [tmp (api/create-tmpfile)] (write-all-bytes tmp (.getBytes "Hello World")) (first (read-all-lines tmp)) => "Hello World")
v 3.0
(defn write-into
([path ^InputStream stream]
(write-into path stream {}))
([path ^InputStream stream opts]
(let [^"[Ljava.nio.file.CopyOption;" opts (->> (:options opts)
(mapv common/option)
(into-array CopyOption))]
(Files/copy stream (path/path path) opts))))
link
(let [tmp (api/create-tmpfile)] (write-into tmp (java.io.FileInputStream. "project.clj") {:options #{:replace-existing}}) (vec (read-all-bytes tmp)) => (vec (read-all-bytes "project.clj")))
- match-filter
- match-single
- visit-directory-post
- visit-directory-pre
- visit-file
- visit-file-failed
- visitor
- walk
match-filter ^
[{:keys [path attrs root include exclude with], :as m}]
matches according to many filters
v 3.0
(defn match-filter
([{:keys [path attrs root include exclude with] :as m}]
(or (and (get with :root) (= (str root) (str path)))
(let [include (if (empty? include)
[{:tag :fn :fn f/T}]
include)
exclude (if (empty? exclude)
[{:tag :fn :fn f/F}]
exclude)]
(and (some #(match-single m %) include)
(not (some #(match-single m %) exclude)))))))
link
(match-filter {}) => true (match-filter {:root (path/path "") :path (path/path "src/hara/test.clj") :include [{:tag :pattern :pattern #"test"}]}) => true (match-filter {:root (path/path "") :path (path/path "src/hara/test.clj") :exclude [{:tag :pattern :pattern #"test"}]}) => false
match-single ^
[{:keys [root path attrs], :as m} {:keys [tag], :as single}]
matches according to the defined filter
v 3.0
(defn match-single
([{:keys [root path attrs] :as m} {:keys [tag] :as single}]
(boolean (case tag
:fn (let [f (:fn single)]
(f path))
:pattern (let [pat (:pattern single)]
(if-not (= (str root) (str path))
(->> (str root)
(count)
(inc)
(subs (str path))
(re-find pat))))
:mode (do (:mode single)
(throw (Exception. "TODO")))))))
link
(match-single {:root (path/path ".") :path (path/path "src/hara/test.clj")} {:tag :pattern :pattern #"src"}) => true (match-single {:root (path/path "src") :path (path/path "src/hara/test.clj")} {:tag :pattern :pattern #"src"}) => false (match-single {:path (path/path "src/hara/test.clj")} {:tag :fn :fn (fn [m] (re-find #"hara" (str m)))}) => true
v 3.0
(defn visit-directory-post
([m]
(let [f (get-in m [:directory :post])
run? (match-filter m)]
(when (and f run?)
(f m))) (common/option :continue)))
link
(visit-directory-post {}) => FileVisitResult/CONTINUE
visit-directory-pre ^
[{:keys [root path attrs accumulate], :as m}]
helper function, triggers before visiting a directory
v 3.0
(defn visit-directory-pre
([{:keys [root path attrs accumulate] :as m}]
(let [f (-> m :directory :pre)
run? (match-filter m)
result (try
(when run?
(if (accumulate :directories)
(swap! (:accumulator m) conj path))
(if f (f m)))
:continue
(catch clojure.lang.ExceptionInfo e
(or (:command (ex-data e))
(throw e))))]
(common/option result))))
link
(visit-directory-pre {:root (path/path ".") :path (path/path "src") :directory {:pre (constantly :continue)} :accumulate #{}}) => FileVisitResult/CONTINUE
visit-file ^
[{:keys [path attrs accumulate], :as m}]
helper function, triggers on visiting a file
v 3.0
(defn visit-file
([{:keys [path attrs accumulate] :as m}]
(let [f (:file m)
run? (match-filter m)
result (try
(when run?
(if (and (accumulate :files)
(->> (into-array [(common/option :nofollow-links)])
(Files/isRegularFile path)))
(swap! (:accumulator m) conj path))
(if f (f m)))
:continue
(catch clojure.lang.ExceptionInfo e
(or (:command (ex-data e))
(throw e))))]
(common/option result))))
link
(visit-file {:path (path/path "project.clj") :file (constantly :continue) :accumulate #{}}) => FileVisitResult/CONTINUE
v 3.0
(defn visit-file-failed
([m]
(common/option
(or (if-let [f (-> m :failed)]
(f m))
:continue))))
link
(visit-file-failed {}) => FileVisitResult/CONTINUE
v 3.0
(defn visitor
([m]
(reify FileVisitor
(preVisitDirectory [_ path attrs]
(visit-directory-pre (assoc m :path path :attrs attrs)))
(postVisitDirectory [_ path error]
(visit-directory-post (assoc m :path path :error error)))
(visitFile [_ path attrs]
(visit-file (assoc m :path path :attrs attrs)))
(visitFileFailed [_ path error]
(visit-file-failed (assoc m :path path :error error))))))
link
(visitor {}) => java.nio.file.FileVisitor
walk ^
[root {:keys [directory file include exclude recursive depth options accumulator accumulate with], :as m}]
visits files based on a directory
v 3.0
(defn walk
([root {:keys [directory file include
exclude recursive depth options
accumulator accumulate with]
:as m}]
(let [directory (cond (nil? directory)
{:pre f/F}
(fn? directory)
{:pre directory}
:else directory)
file (cond (nil? file) f/F
:else directory)
options (-> (map common/+file-visit-options+ options) (set) (disj nil))
depth (or (cond (false? recursive) 1
(true? recursive) Integer/MAX_VALUE)
depth
Integer/MAX_VALUE)
root (path/path root)
accumulate (or accumulate #{:files :directories})
accumulator (or accumulator (atom []))
include (map common/characterise-filter include)
exclude (map common/characterise-filter exclude)
with (or with #{})
state (merge m {:root root
:directory directory
:depth depth
:include include
:exclude exclude
:options options
:with with
:accumulate accumulate
:accumulator accumulator})
visitor (visitor state)]
(Files/walkFileTree (:root state)
(:options state)
(:depth state)
visitor)
@accumulator)))
link
(walk "src" {:accumulate #{:directories}}) => vector? (walk "src" {:accumulator (atom {}) :accumulate #{} :file (fn [{:keys [path attrs accumulator]}] (swap! accumulator assoc (str path) (.toMillis (.lastAccessTime ^BasicFileAttributes attrs))))}) => map?
- archive
- create
- extract
- has?
- insert
- list
- open
- open-and
- path
- remove
- stream
- supported
- url
- write
- zip-system?
v 3.0
(defn archive
([archive root]
(let [ach (open archive)
res (->> (fs/select root {:exclude [fs/directory?]})
(protocol.archive/-archive ach root))]
(.close ach)
res))
([archive root inputs]
(protocol.archive/-archive (open archive) root inputs)))
link
(archive "test-scratch/hello.jar" "src") => coll?
v 3.0
(defn create
([archive]
(if (fs/exists? archive)
(throw (ex-info "Archive already exists" {:path archive}))
(let [path (fs/path archive)]
(do (fs/create-directory (fs/parent path))
(FileSystems/newFileSystem
(URI. (str "jar:file:" path))
{"create" "true"}))))))
link
(fs/delete "test-scratch/hello.jar") (create "test-scratch/hello.jar") => zip-system?
extract ^
[archive] [archive output] [archive output entries]
extracts all file from an archive
v 3.0
(defn extract
([archive]
(extract archive (fs/parent (url archive))))
([archive output]
(extract archive output (list archive)))
([archive output entries]
(protocol.archive/-extract (open archive {:create false}) output entries)))
link
(extract "test-scratch/hello.jar") => coll? (extract "test-scratch/hello.jar" "test-scratch/output") (fs/delete "test-scratch/hello.jar") (fs/delete "test-scratch/hara") (fs/delete "test-scratch/output") (fs/delete "test-scratch/select")
v 3.0
(defn has?
([archive entry]
(open-and archive {:create false} #(protocol.archive/-has? % entry))))
link
(has? "test-scratch/hello.jar" "world.java") => false
v 3.0
(defn insert
([archive entry input]
(open-and archive {} #(protocol.archive/-insert % entry input))))
link
(open "test-scratch/hello.jar" {:create true}) (insert "test-scratch/hello.jar" "project.clj" "project.clj") => fs/path?
v 3.0
(defn list
([archive]
(open-and archive {:create false} protocol.archive/-list)))
link
(map str (list "test-scratch/hello.jar")) => ["/"]
open ^
[archive] [archive opts]
either opens an existing archive or creates one if it doesn't exist
v 3.0
(defn ^ZipFileSystem open
([archive]
(open archive {:create true}))
([archive opts]
(cond (instance? FileSystem archive)
archive
:else
(let [path (fs/path archive)]
(cond (fs/exists? path)
(FileSystems/newFileSystem path)
(:create opts)
(create archive)
:else
(throw (ex-info "archive does not exist"
{:path archive})))))))
link
(open "test-scratch/hello.jar" {:create true}) => zip-system?
open-and ^
[archive opts callback]
helper function for opening an archive and performing a single operation
v 3.0
(defn open-and
([archive opts callback]
(let [farchive (open archive opts)
result (callback farchive)]
(if (string? archive) (.close farchive))
result)))
link
(->> (open-and "test-scratch/hello.jar" {:create false} #(protocol.archive/-list %)) (map str)) => ["/"]
v 3.0
(defn path
([archive entry]
(open-and archive {:create false} #(protocol.archive/-path % entry))))
link
(-> (open "test-scratch/hello.jar") (path "world.java") (str)) => "world.java"
v 3.0
(defn remove
([archive entry]
(open-and archive {:create false} #(protocol.archive/-remove % entry))))
link
(do (fs/delete "test-scratch/remove.jar") (open "test-scratch/remove.jar" {:create true}) (insert "test-scratch/remove.jar" "project.clj" "project.clj") (remove "test-scratch/remove.jar" "project.clj") (has? "test-scratch/remove.jar" "project.clj")) => false (fs/delete "test-scratch/remove.jar")
v 3.0
(defn stream
([archive entry]
(protocol.archive/-stream (open archive {:create false}) entry)))
link
(do (fs/delete "test-scratch/stream.jar") (open "test-scratch/stream.jar" {:create true}) (insert "test-scratch/stream.jar" "project.clj" "project.clj") (slurp (stream "test-scratch/stream.jar" "project.clj"))) => (slurp "project.clj") (fs/delete "test-scratch/stream.jar")
v 3.0
(defn url
([archive]
(protocol.archive/-url archive)))
link
(url (open "test-scratch/hello.jar")) => (str (fs/path "test-scratch/hello.jar"))
v 3.0
(defn write
([archive entry stream]
(open-and archive {:create false} #(protocol.archive/-write % entry stream))))
link
(do (fs/delete "test-scratch/write.jar") (open "test-scratch/write.jar" {:create true}) (write "test-scratch/write.jar" "test.stuff" (binary/input-stream (.getBytes "Hello World"))) (slurp (stream "test-scratch/write.jar" "test.stuff"))) => "Hello World" (fs/delete "test-scratch/write.jar")
- *defaults*
- *filewatchers*
- add-io-watch
- event-kinds
- event-lookup
- event-types
- kind-lookup
- list-io-watch
- pattern
- process-event
- register-entry
- register-path
- register-sub-directory
- remove-io-watch
- run-watcher
- start-watcher
- stop-watcher
- watch-callback
- watcher
v 3.0
(defn add-io-watch
([obj k f opts]
(let [path (.getCanonicalPath ^java.io.File obj)
_ (if-let [wt (get-in @*filewatchers* [path k :watcher])]
(protocol.watch/-remove-watch obj k nil))
cb (watch-callback f obj k)
wt (start-watcher (watcher path cb opts))]
(swap! *filewatchers* assoc-in [path k] {:watcher wt :function f})
obj)))
link
(let [dir (io/file "test-scratch/io-watch") _ (.mkdirs dir)] (add-io-watch dir :save (fn [_ _ _ _]) {:recursive false})) => java.io.File
event-kinds ^
NONE
(def event-kinds [StandardWatchEventKinds/ENTRY_CREATE
StandardWatchEventKinds/ENTRY_DELETE
StandardWatchEventKinds/ENTRY_MODIFY])
link
v 3.0
(defn list-io-watch
([obj _]
(let [path (.getCanonicalPath ^java.io.File obj)]
(->> (get @*filewatchers* path)
(collection/map-vals :function)))))
link
(let [dir (io/file "test-scratch/io-watch-list") _ (.mkdirs dir) _ (add-io-watch dir :save (fn [_ _ _ _]) {:recursive false})] (list-io-watch dir nil)) => (contains {:save fn?})
v 3.0
(defn pattern
([s]
(-> s
(clojure.string/replace #"." "\\\Q.\\\E")
(clojure.string/replace #"*" ".+")
(re-pattern))))
link
(pattern ".*") => #"Q.E.+" (pattern "*.jar") => #".+Q.Ejar"
v 3.0
(defn process-event
([watcher kind ^java.io.File file]
(let [{:keys [options callback excludes filters kinds]} watcher
filepath (.getPath file)
filename (.getName file)]
(when (and (get kinds kind)
(or (empty? filters)
(some #(re-find % filename) filters))
(or (empty? excludes)
(not (some #(re-find % filename) excludes))))
(case (:mode options)
:async (future (callback (kind-lookup kind) file))
:sync (callback (kind-lookup kind) file))))))
link
(let [out (atom nil) watcher {:options {:mode :sync} :callback (fn [type file] (reset! out [type (.getName ^File file)])) :excludes [] :filters [] :kinds #{java.nio.file.StandardWatchEventKinds/ENTRY_CREATE}}] (process-event watcher java.nio.file.StandardWatchEventKinds/ENTRY_CREATE (io/file "project.clj")) @out) => [:create "project.clj"]
v 3.0
(defn register-entry
([service path]
(let [entry (Paths/get path (make-array String 0))]
(.register entry service (into-array event-kinds)))))
link
(let [service (.newWatchService (FileSystems/getDefault)) key (register-entry service "src")] (try (instance? java.nio.file.WatchKey key) (finally (.close service)))) => true
register-path ^
[{:keys [service], :as watcher} path]
registers either a file or a path to the watcher
v 3.0
(defn register-path
([{:keys [service] :as watcher} path]
(let [f (io/file path)]
(cond (.isDirectory f)
(register-sub-directory watcher path)
(.exists f)
(register-entry service path)))))
link
(let [service (.newWatchService (FileSystems/getDefault)) root (.getCanonicalPath (io/file "src")) watcher (assoc (watcher [root] (fn [_ _]) {:recursive false}) :root root :service service :seen (atom #{}) :excludes [] :includes [])] (try (= watcher (register-path watcher root)) (finally (.close service)))) => true
register-sub-directory ^
[watcher dir-path]
registers a directory to an existing watcher
v 3.0
(defn register-sub-directory
([watcher dir-path]
(let [{:keys [root seen options service excludes includes]} watcher]
(when (and (or (= dir-path root)
(empty? includes)
(some #(re-find % (subs dir-path (-> root count inc))) includes))
(not (or (and seen (get @seen dir-path))
(some #(re-find % (last (clojure.string/split dir-path #"/"))) excludes))))
(register-entry service dir-path)
(if seen (swap! seen conj dir-path))
(if (:recursive options)
(doseq [^java.io.File f (.listFiles (io/file dir-path))]
(when (. f isDirectory)
(register-sub-directory watcher (.getCanonicalPath f))))))
watcher)))
link
(let [service (.newWatchService (FileSystems/getDefault)) seen (atom #{}) root (.getCanonicalPath (io/file "src")) subdir (.getCanonicalPath (io/file "src/std")) watcher (assoc (watcher [root] (fn [_ _]) {:recursive false}) :root root :service service :seen seen :excludes [] :includes [])] (try (register-sub-directory watcher subdir) @seen (finally (.close service)))) => (contains #{(.getCanonicalPath (io/file "src/std"))})
v 3.0
(defn remove-io-watch
([obj k _]
(let [path (.getCanonicalPath ^java.io.File obj)
wt (get-in @*filewatchers* [path k :watcher])]
(if-not (nil? wt)
(if (stop-watcher wt)
(swap! *filewatchers* dissoc path)
true)
false))))
link
(let [dir (io/file "test-scratch/io-watch-remove") _ (.mkdirs dir) _ (add-io-watch dir :save (fn [_ _ _ _]) {:recursive false})] (remove-io-watch dir :save nil) (list-io-watch dir nil)) => {}
v 3.0
(defn run-watcher
([watcher]
(let [^java.nio.file.WatchKey wkey
(.take ^java.nio.file.WatchService (:service watcher))]
(doseq [^java.nio.file.WatchEvent event (.pollEvents wkey)
:when (not= (.kind event)
StandardWatchEventKinds/OVERFLOW)]
(let [kind (.kind event)
^java.nio.file.Path path (.watchable wkey)
^java.nio.file.Path context (.context event)
^java.nio.file.Path res-path (.resolve path context)
^java.io.File file (.toFile res-path)]
(if (and (= kind StandardWatchEventKinds/ENTRY_CREATE)
(.isDirectory file)
(-> watcher :options :recursive))
(register-sub-directory watcher (.getPath file)))
(if-not (.isDirectory file)
(process-event watcher kind file))))
(.reset wkey)
(recur watcher))))
link
(let [dir (str (io/file "test-scratch/watch-run")) out (promise) event (reify java.nio.file.WatchEvent (kind [_] java.nio.file.StandardWatchEventKinds/ENTRY_CREATE) (count [_] 1) (context [_] (Paths/get "hello.watch" (make-array String 0)))) key (reify java.nio.file.WatchKey (isValid [_] true) (pollEvents [_] [event]) (reset [_] true) (cancel [_] nil) (watchable [_] (Paths/get dir (make-array String 0)))) service (let [calls (atom 0)] (reify java.nio.file.WatchService (close [_] nil) (poll [_] nil) (poll [_ _ _] nil) (take [_] (if (= 1 (swap! calls inc)) key (throw (java.nio.file.ClosedWatchServiceException.))))))] (try (run-watcher {:paths [dir] :callback (fn [type file] (deliver out [type (.getName ^File file)])) :options {:recursive false :mode :sync} :root dir :service service :seen (atom #{}) :filters [] :excludes [] :includes [] :kinds #{java.nio.file.StandardWatchEventKinds/ENTRY_CREATE}}) (catch java.nio.file.ClosedWatchServiceException _)) @out) => [:create "hello.watch"]
v 3.0
(defn start-watcher
([watcher]
(let [{:keys [types filter exclude include]} (:options watcher)
^java.nio.file.WatchService service (.newWatchService (FileSystems/getDefault))
seen (atom #{})
kinds (if (= types :all)
(set event-kinds)
(->> types (map event-lookup) set))
filters (->> filter (map pattern))
excludes (->> exclude (map pattern))
includes (->> include (map pattern))
watcher (->> (assoc watcher
:root (first (:paths watcher))
:service service
:seen seen
:filters filters
:excludes excludes
:includes includes
:kinds kinds))
watcher (reduce register-path watcher (:paths watcher))]
(assoc watcher :running (future (run-watcher watcher))))))
link
(let [dir (str (io/file "test-scratch/watch-start")) _ (.mkdirs (io/file dir)) wt (start-watcher (watcher [dir] (fn [_ _]) {:recursive false}))] (try (future? (:running wt)) (finally (stop-watcher wt)))) => true
v 3.0
(defn stop-watcher
([watcher]
(.close ^java.nio.file.WatchService (:service watcher))
(future-cancel (:running watcher)) (dissoc watcher :running :service :seen)))
link
(let [dir (str (io/file "test-scratch/watch-stop")) _ (.mkdirs (io/file dir)) wt (start-watcher (watcher [dir] (fn [_ _]) {:recursive false}))] (contains? (stop-watcher wt) :running)) => false
v 3.0
(defn watch-callback
([f root k]
(fn [type file]
(f root k nil [type file]))))
link
(let [out (promise)] ((watch-callback (fn [root k _ [cmd file]] (deliver out [(.getName ^File root) k cmd (.getName ^File file)])) (io/file ".") :save) :create (io/file "project.clj")) @out) => ["." :save :create "project.clj"]
5 std.fs: A Comprehensive Summary (including submodules)
The std.fs module provides a comprehensive and idiomatic Clojure interface for interacting with the filesystem, building upon Java's java.nio.file package. It offers a rich set of functions for path manipulation, file and directory operations (create, copy, move, delete, list, walk), attribute management, and even watching for filesystem changes. The module aims to simplify common filesystem tasks and provide robust, cross-platform capabilities.
5.1 std.fs (Main Namespace)
This namespace serves as the primary entry point for filesystem operations, aggregating and re-exporting key functionalities from its submodules. It also provides utilities for converting between Clojure namespaces and file paths.
Key Re-exported Functions:
- From
std.fs.api: All file/directory manipulation functions (create-directory,copy,delete,move,select,list, etc.).n Fromstd.fs.path: All path manipulation functions (path,file-name,parent,exists?,directory?,file?,input-stream,output-stream, etc.).n Fromstd.fs.attribute:attributes,set-attributes.n* Fromstd.fs.common:option.
Key Functions:
ns->file: Converts a Clojure namespace string (e.g.,"std.fs-test") to a file path string (e.g.,"std/fs_test").nfile->ns: Converts a file path string to a Clojure namespace string.nread-code: Reads a Clojure source file and returns a lazy sequence of its top-level forms.nget-namespace: Extracts the namespace symbol from a sequence of Clojure forms.nfile-namespace: Reads the namespace of a given Clojure source file.
5.2 std.fs.api (High-Level File Operations)
This sub-namespace provides high-level functions for common filesystem operations, such as creating, copying, moving, and deleting files and directories. It leverages std.fs.walk for recursive operations and filtering.
Key Functions:
create-directory: Creates a directory, including any necessary parent directories.ncreate-symlink: Creates a symbolic link.ncreate-tmpfile: Creates a temporary file.ncreate-tmpdir: Creates a temporary directory.nselect: Selects files and directories within a root path, with options for filtering.nlist: Lists files and attributes for a given directory, with options for recursion.ncopy: Copies files and directories from a source to a target, preserving directory structure.ncopy-single: Copies a single file.ncopy-into: Copies selected files from a source into a target directory.ndelete: Deletes files and directories, with options for filtering.nmove: Moves files and directories.
5.3 std.fs.archive (Archive (ZIP/JAR) Manipulation)
This sub-namespace provides functions for creating, opening, and manipulating ZIP and JAR archives, treating them as virtual filesystems.
Core Concepts:
IArchiveProtocol: Defines methods for interacting with archive filesystems (e.g.,url,path,list,has?,archive,extract,insert,remove,write,stream).n*ZipFileSystem: Extends theIArchiveprotocol to work with Java'sZipFileSystem.
Key Functions:
zip-system?: Checks if an object is aZipFileSystem.ncreate: Creates a new ZIP/JAR archive.nopen: Opens an existing archive or creates a new one.nopen-and: Opens an archive, performs an operation, and then closes it.nurl: Returns the URL of an archive.npath: Returns aPathobject for an entry within an archive.nlist: Lists all entries in an archive.nhas?: Checks if an archive contains a specific entry.narchive: Adds files to an archive.nextract: Extracts entries from an archive.ninsert: Inserts a file into an archive.nremove: Removes an entry from an archive.nwrite: Writes a stream to an entry within an archive.n*stream: Creates anInputStreamfor an entry within an archive.
5.4 std.fs.attribute (File Attributes)
This sub-namespace provides functions for reading and setting various file attributes, including permissions, ownership, and timestamps.
Core Concepts:
FileAttribute: Java's representation of a file attribute.n* POSIX File Permissions: Supports POSIX-style file permissions (e.g.,rwxr-xr-x).
Key Functions:
to-mode-string: Converts numeric file modes (e.g., "455") to string representations (e.g., "r--r-xr-x").nto-mode-number: Converts string file modes to numeric representations.nto-permissions: Converts numeric file modes to a set ofPosixFilePermissionkeywords.nfrom-permissions: Converts a set ofPosixFilePermissionkeywords to numeric file modes.nowner: Returns the owner of a file.nlookup-owner: Looks up a user principal by name.nset-owner: Sets the owner of a file.nlookup-group: Looks up a group principal by name.nattr: Creates aFileAttributeobject.nattr-value: Adjusts attribute values for input (e.g., converting owner name toUserPrincipal).nmap->attr-array: Converts a Clojure map of attributes to aFileAttributearray.nattrs->map: Converts a map ofFileAttributeobjects to a Clojure map.nattributes: Retrieves all attributes for a given path.n*set-attributes: Sets attributes for a given path.
5.5 std.fs.common (Filesystem Common Utilities)
This sub-namespace defines common constants and utility functions used across the std.fs module, such as system-specific separators, OS detection, and option handling.
Key Functions:
*cwd*,*sep*,*os*,*home*,*tmp-dir*: Dynamic vars for common system properties.n*no-follow*: DefaultLinkOptionfor not following symbolic links.n*system*: Detects the operating system type (:dosor:unix).noption: Retrievesjava.nio.file.CopyOption,StandardOpenOption, etc., by keyword.npattern: Converts a glob-like string (e.g.,*.clj) to ajava.util.regex.Pattern.ntag-filter: Adds a:tagto a filter map.ncharacterise-filter: Converts various filter inputs (string, regex, function, map) into a standardized filter map.
5.6 std.fs.interop (Filesystem Interoperability)
This sub-namespace provides interoperability with clojure.java.io, extending the IOFactory protocol for java.nio.file.Path objects.
Key Functions:
- Extends
java.nio.file.Pathtoclojure.java.io/IOFactory, allowingPathobjects to be used directly withclojure.java.io/input-streamandclojure.java.io/output-stream.
5.7 std.fs.path (Path Manipulation)
This sub-namespace provides functions for creating, manipulating, and querying java.nio.file.Path objects, offering a robust and platform-agnostic way to work with file paths.
Key Functions:
normalise: Normalizes a path string, handling~for home directory and resolving relative paths.npath: Creates ajava.nio.file.Pathobject from various inputs (string, vector, URI, URL, File).npath?: Checks if an object is aPath.nsection: Creates aPathobject without normalization.nto-file: Converts aPathto ajava.io.File.nfile-name: Returns the last element of a path.nfile-system: Returns theFileSystemfor a path.nnth-segment,segment-count: Accesses path segments.nparent: Returns the parent path.nroot: Returns the root path.nrelativize: Creates a relative path between two paths.nsubpath: Returns a sub-sequence of path segments.nfile-suffix: Returns the file extension as a keyword.ndirectory?,executable?,set-executable,permissions,typestring,exists?,hidden?,file?,link?,readable?,writable?,empty-directory?: Predicates and functions for querying file/directory properties.ninput-stream,output-stream: OpensInputStreamorOutputStreamfor a path.nread-all-lines,read-all-bytes: Reads file content.nfile: Returns ajava.io.Fileobject.nlast-modified: Returns the last modified timestamp.nwrite-into: Writes anInputStreamto a path.n*write-all-bytes: Writes a byte array to a file.
5.8 std.fs.walk (Filesystem Walking)
This sub-namespace provides a powerful and flexible mechanism for traversing filesystem trees, allowing for custom actions to be performed on files and directories, and supporting complex filtering rules.
Core Concepts:
FileVisitor: Implements Java'sjava.nio.file.FileVisitorinterface.n Filter System: Supportsincludeandexcludefilters based on patterns or custom functions.n Hooks: Allows definingpreandposthooks for directories, and afilehook for files.
Key Functions:
match-single: Matches a single filter against a path.nmatch-filter: Matches a path against multiple include/exclude filters.nvisit-directory-pre,visit-directory-post,visit-file,visit-file-failed: Helper functions forFileVisitorcallbacks.nvisitor: Constructs aFileVisitorinstance.nwalk: The main function for traversing a filesystem tree, applying filters and hooks.
5.9 std.fs.watch (Filesystem Watching)
This sub-namespace provides functionality for watching filesystem directories for changes (create, delete, modify events), allowing applications to react to filesystem events in real-time.
Core Concepts:
WatchService: Leverages Java'sjava.nio.file.WatchServicefor event notification.nWatcherrecord: Encapsulates the state of a filesystem watcher.n Event Kinds: SupportsENTRY_CREATE,ENTRY_DELETE,ENTRY_MODIFYevents.n* Filters: Allows filtering events by filename patterns.
Key Functions:
pattern: Creates a regex pattern from a glob-like string.nregister-entry: Registers a path with aWatchService.nregister-sub-directory: Recursively registers subdirectories for watching.nregister-path: Registers a file or directory for watching.nprocess-event: Processes a single watch event.nrun-watcher: The main loop for processing watch events.nstart-watcher: Starts a filesystem watcher in a separate thread.nstop-watcher: Stops a filesystem watcher.nwatcher: Creates aWatcherrecord.nwatch-callback: Creates a callback function for watch events.nadd-io-watch,list-io-watch,remove-io-watch: Functions for managing watches onjava.io.Fileobjects, extendingstd.protocol.watch/IWatch.
5.10 Usage Pattern:
The std.fs module is essential for any Clojure application that needs to interact with the local filesystem. It provides:
- Robust File I/O: A consistent and powerful API for file and directory operations.n Cross-Platform Compatibility: Built on
java.nio.file, ensuring consistent behavior across different operating systems.n Advanced Features: Support for file attributes, symbolic links, and archive manipulation.n Event-Driven Filesystem Interaction: Real-time monitoring of filesystem changes.n Code Analysis: Utilities for reading and parsing Clojure source files.
By offering a comprehensive and well-designed filesystem API, std.fs simplifies complex filesystem tasks and enables the foundation-base project to manage its code and resources effectively.