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+ ^

NONE
(def ^:private +empty-string-array+
  (make-array String 0))
link

attributes ^

[path]
Added 3.0

shows all attributes for a given path

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]
Added 3.0

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]
Added 3.0

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]
Added 3.0

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")

create-directory ^

[path] [path attrs]
Added 3.0

creates a directory on the filesystem

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]
Added 3.0

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")

create-tmpdir ^

[] [prefix]
Added 3.0

creates a temp directory on the filesystem

v 3.0
(defn create-tmpdir
  ([]
   (create-tmpdir ""))
  ([prefix]
   (Files/createTempDirectory prefix (make-array FileAttribute 0))))
link
(create-tmpdir) => java.nio.file.Path

create-tmpfile ^

[] [contents]
Added 3.0

creates a tempory file

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

delete ^

[root] [root opts]
Added 3.0

copies all specified files from one to another

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?

directory? ^

[path]
Added 3.0

checks whether a file is a directory

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]
Added 3.0

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

executable? ^

[path]
Added 3.0

checks whether a file is executable

v 3.0
(defn executable?
  ([path]
   (Files/isExecutable (path/path path))))
link
(executable? "project.clj") => boolean? (executable? "/usr/bin/whoami") => true

exists? ^

[path]
Added 3.0

checks whether a file exists

v 3.0
(defn exists?
  ([path]
   (Files/exists (path/path path) common/*no-follow*)))
link
(exists? "project.clj") => true (exists? "NON.EXISTENT") => false

file ^

[path]
Added 3.0

returns the input as a file

v 3.0
(defn ^File file
  ([path]
   (path/to-file (path/path path))))
link
(file "project.clj") => java.io.File

file->ns ^

[file]
Added 3.0

converts a file string to an ns string

v 3.0
(defn file->ns
  ([^String file]
   (-> file
       (.replaceAll "/" ".")
       (.replaceAll "_" "-"))))
link
(file->ns "std/fs_test") => "std.fs-test"

file-name ^

[x]
Added 3.0

returns the last section of the path

v 3.0
(defn file-name
  ([x]
   (.getFileName (path x))))
link
(str (file-name "src/std")) => "std"

file-namespace ^

[path]
Added 3.0

reads the namespace of the given path

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

file-suffix ^

[file]
Added 3.0

encodes the type of file as a keyword

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

file-system ^

[x]
Added 3.0

returns the filesystem governing the path

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

file? ^

[path]
Added 3.0

checks whether a file is not a link or directory

v 3.0
(defn file?
  ([path]
   (Files/isRegularFile (path/path path) common/*no-follow*)))
link
(file? "project.clj") => true (file? "src") => false

get-namespace ^

[forms]
Added 4.0

gets the namespace given forms

v 4.0
(defn get-namespace
  ([forms]
   (->> forms
        (filter #(-> % first (= 'ns)))
        first
        second)))
link
(get-namespace '[(ns hello)]) => 'hello

hidden? ^

[path]
Added 3.0

checks whether a file is hidden

v 3.0
(defn hidden?
  ([path]
   (Files/isHidden (path/path path))))
link
(hidden? ".gitignore") => true (hidden? "project.clj") => false

input-stream ^

[path] [path opts]
Added 3.0

opens a file as an input-stream

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)

last-modified ^

[path]
Added 3.0

returns the last modified time as a long

v 3.0
(defn last-modified
  ([path]
   (.lastModified (path/file path))))
link
(last-modified "project.clj") => integer?

link? ^

[path]
Added 3.0

checks whether a file is a link

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")

list ^

[root] [root opts]
Added 3.0

lists the files and attributes for a given directory

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)

move ^

[source target] [source target opts]
Added 3.0

moves a file or directory

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")

normalise ^

[s]
Added 3.0

creates a string that takes notice of the user home

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"

ns->file ^

[ns]
Added 3.0

converts an ns string to a file string

v 3.0
(defn ^String ns->file
  ([ns]
   (-> (str ns)
       (.replaceAll "\." "/")
       (.replaceAll "-" "_"))))
link
(ns->file 'std.fs-test) => "std/fs_test"

nth-segment ^

[x i]
Added 3.0

returns the nth segment of a given path

v 3.0
(defn nth-segment
  ([x i]
   (.getName (path x) i)))
link
(str (nth-segment "/usr/local/bin" 1)) => "local"

option ^

[] [k]
Added 3.0

shows all options for file operations

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

output-stream ^

[path] [path opts]
Added 3.0

opens a file as an output-stream

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))

parent ^

[x]
Added 3.0

returns the parent of the given path

v 3.0
(defn parent
  ([x]
   (.getParent (path x))))
link
(str (parent "/usr/local/bin")) => "/usr/local"

path ^

[x] [s & more]
Added 3.0

creates a `java.nio.file.Path object

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)

path-functionality ^

Added 3.0

returns a java.nio.file.Path object

v 3.0
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")

path? ^

[x]
Added 3.0

checks to see if the object is of type Path

v 3.0
(defn path?
  ([x]
   (instance? Path x)))
link
(path? (path "/home")) => true

permissions ^

[path]
Added 3.0

returns the permissions for a given file

v 3.0
(defn permissions
  ([path]
   (-> (path/path path)
       (Files/getPosixFilePermissions  common/*no-follow*)
       (PosixFilePermissions/toString))))
link
(permissions "src") => string?

read-all-bytes ^

[path]
Added 3.0

opens a file and reads the contents as a byte array

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)))

read-all-lines ^

[path]
Added 3.0

opens a file and reads the contents as an array of lines

v 3.0
(defn read-all-lines
  ([path]
   (Files/readAllLines (path/path path))))
link
(read-all-lines "project.clj") => (partial every? string?)

read-code ^

[path] [path f]
Added 3.0

takes a file and returns a lazy seq of top-level forms

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)

readable? ^

[path]
Added 3.0

checks whether a file is readable

v 3.0
(defn readable?
  ([path]
   (Files/isReadable (path/path path))))
link
(readable? "project.clj") => true

relativize ^

[x other]
Added 3.0

returns one path relative to another

v 3.0
(defn relativize
  ([x other]
   (.relativize (path x) (path other))))
link
(str (relativize "test" "src/std")) => "../src/std"

root ^

[x]
Added 3.0

returns the root path

v 3.0
(defn root
  ([x]
   (.getRoot (path x))))
link
(str (root "/usr/local/bin")) => "/"

section ^

[s & more]
Added 3.0

path object without normalisation

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"

segment-count ^

[x]
Added 3.0

returns the number of segments of a given path

v 3.0
(defn segment-count
  ([x]
   (.getNameCount (path x))))
link
(segment-count "/usr/local/bin") => 3

select ^

[root] [root opts]
Added 3.0

selects all the files in a directory

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)

set-attributes ^

[path m]
Added 3.0

sets all attributes for a given path

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

set-executable ^

[path]
Added 3.0

sets a file to be executable

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)

subpath ^

[x start end]
Added 3.0

returns the subpath of a given path

v 3.0
(defn subpath
  ([x start end]
   (.subpath (path x) start end)))
link
(str (subpath "/usr/local/bin/hello" 1 3)) => "local/bin"

to-file ^

[path]
Added 3.0

creates a java.io.File object

v 3.0
(defn to-file
  ([^Path path]
   (.toFile path)))
link
(to-file (section "project.clj")) => (all java.io.File #(-> % str (= "project.clj")))

typestring ^

[path]
Added 3.0

returns the shorthand string for a given entry

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") => "-"

writable? ^

[path]
Added 3.0

checks whether a file is writable

v 3.0
(defn writable?
  ([path]
   (Files/isWritable (path/path path))))
link
(writable? "project.clj") => true

write-all-bytes ^

[path bytes] [path bytes opts]
Added 3.0

writes a byte-array to file

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")

write-into ^

[path stream] [path stream opts]
Added 3.0

writes a stream to a path

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+ ^

NONE
(def ^:private +empty-string-array+
  (make-array String 0))
link

directory? ^

[path]
Added 3.0

checks whether a file is a directory

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]
Added 3.0

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

executable? ^

[path]
Added 3.0

checks whether a file is executable

v 3.0
(defn executable?
  ([path]
   (Files/isExecutable (path/path path))))
link
(executable? "project.clj") => boolean? (executable? "/usr/bin/whoami") => true

exists? ^

[path]
Added 3.0

checks whether a file exists

v 3.0
(defn exists?
  ([path]
   (Files/exists (path/path path) common/*no-follow*)))
link
(exists? "project.clj") => true (exists? "NON.EXISTENT") => false

file ^

[path]
Added 3.0

returns the input as a file

v 3.0
(defn ^File file
  ([path]
   (path/to-file (path/path path))))
link
(file "project.clj") => java.io.File

file-name ^

[x]
Added 3.0

returns the last section of the path

v 3.0
(defn file-name
  ([x]
   (.getFileName (path x))))
link
(str (file-name "src/std")) => "std"

file-suffix ^

[file]
Added 3.0

encodes the type of file as a keyword

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

file-system ^

[x]
Added 3.0

returns the filesystem governing the path

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

file? ^

[path]
Added 3.0

checks whether a file is not a link or directory

v 3.0
(defn file?
  ([path]
   (Files/isRegularFile (path/path path) common/*no-follow*)))
link
(file? "project.clj") => true (file? "src") => false

hidden? ^

[path]
Added 3.0

checks whether a file is hidden

v 3.0
(defn hidden?
  ([path]
   (Files/isHidden (path/path path))))
link
(hidden? ".gitignore") => true (hidden? "project.clj") => false

input-stream ^

[path] [path opts]
Added 3.0

opens a file as an input-stream

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)

last-modified ^

[path]
Added 3.0

returns the last modified time as a long

v 3.0
(defn last-modified
  ([path]
   (.lastModified (path/file path))))
link
(last-modified "project.clj") => integer?

link? ^

[path]
Added 3.0

checks whether a file is a link

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")

normalise ^

[s]
Added 3.0

creates a string that takes notice of the user home

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"

nth-segment ^

[x i]
Added 3.0

returns the nth segment of a given path

v 3.0
(defn nth-segment
  ([x i]
   (.getName (path x) i)))
link
(str (nth-segment "/usr/local/bin" 1)) => "local"

output-stream ^

[path] [path opts]
Added 3.0

opens a file as an output-stream

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))

parent ^

[x]
Added 3.0

returns the parent of the given path

v 3.0
(defn parent
  ([x]
   (.getParent (path x))))
link
(str (parent "/usr/local/bin")) => "/usr/local"

path ^

[x] [s & more]
Added 3.0

creates a `java.nio.file.Path object

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)

path-functionality ^

Added 3.0

returns a java.nio.file.Path object

v 3.0
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")

path? ^

[x]
Added 3.0

checks to see if the object is of type Path

v 3.0
(defn path?
  ([x]
   (instance? Path x)))
link
(path? (path "/home")) => true

permissions ^

[path]
Added 3.0

returns the permissions for a given file

v 3.0
(defn permissions
  ([path]
   (-> (path/path path)
       (Files/getPosixFilePermissions  common/*no-follow*)
       (PosixFilePermissions/toString))))
link
(permissions "src") => string?

read-all-bytes ^

[path]
Added 3.0

opens a file and reads the contents as a byte array

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)))

read-all-lines ^

[path]
Added 3.0

opens a file and reads the contents as an array of lines

v 3.0
(defn read-all-lines
  ([path]
   (Files/readAllLines (path/path path))))
link
(read-all-lines "project.clj") => (partial every? string?)

readable? ^

[path]
Added 3.0

checks whether a file is readable

v 3.0
(defn readable?
  ([path]
   (Files/isReadable (path/path path))))
link
(readable? "project.clj") => true

relativize ^

[x other]
Added 3.0

returns one path relative to another

v 3.0
(defn relativize
  ([x other]
   (.relativize (path x) (path other))))
link
(str (relativize "test" "src/std")) => "../src/std"

root ^

[x]
Added 3.0

returns the root path

v 3.0
(defn root
  ([x]
   (.getRoot (path x))))
link
(str (root "/usr/local/bin")) => "/"

section ^

[s & more]
Added 3.0

path object without normalisation

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"

segment-count ^

[x]
Added 3.0

returns the number of segments of a given path

v 3.0
(defn segment-count
  ([x]
   (.getNameCount (path x))))
link
(segment-count "/usr/local/bin") => 3

set-executable ^

[path]
Added 3.0

sets a file to be executable

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)

subpath ^

[x start end]
Added 3.0

returns the subpath of a given path

v 3.0
(defn subpath
  ([x start end]
   (.subpath (path x) start end)))
link
(str (subpath "/usr/local/bin/hello" 1 3)) => "local/bin"

to-file ^

[path]
Added 3.0

creates a java.io.File object

v 3.0
(defn to-file
  ([^Path path]
   (.toFile path)))
link
(to-file (section "project.clj")) => (all java.io.File #(-> % str (= "project.clj")))

typestring ^

[path]
Added 3.0

returns the shorthand string for a given entry

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") => "-"

writable? ^

[path]
Added 3.0

checks whether a file is writable

v 3.0
(defn writable?
  ([path]
   (Files/isWritable (path/path path))))
link
(writable? "project.clj") => true

write-all-bytes ^

[path bytes] [path bytes opts]
Added 3.0

writes a byte-array to file

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")

write-into ^

[path stream] [path stream opts]
Added 3.0

writes a stream to a path

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 ^

[{:keys [path attrs root include exclude with], :as m}]
Added 3.0

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}]
Added 3.0

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

visit-directory-post ^

[m]
Added 3.0

helper function, triggers after visiting a directory

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}]
Added 3.0

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}]
Added 3.0

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

visit-file-failed ^

[m]
Added 3.0

helper function, triggers on after a file cannot be visited

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

visitor ^

[m]
Added 3.0

contructs the clojure wrapper for `java.nio.file.FileVisitor`

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}]
Added 3.0

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 ^

[archive root] [archive root inputs]
Added 3.0

puts files into an archive

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?

create ^

[archive]
Added 3.0

creats a zip file

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]
Added 3.0

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")

has? ^

[archive entry]
Added 3.0

checks if the archive has a particular entry

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

insert ^

[archive entry input]
Added 3.0

inserts a file to an entry within the archive

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?

list ^

[archive]
Added 3.0

lists all the entries in the archive

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]
Added 3.0

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]
Added 3.0

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)) => ["/"]

path ^

[archive entry]
Added 3.0

returns the url of the archive

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"

remove ^

[archive entry]
Added 3.0

removes an entry from the archive

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")

stream ^

[archive entry]
Added 3.0

creates a stream for an entry wthin the archive

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")

supported ^

NONE
(def supported #{:zip :jar})
link

url ^

[archive]
Added 3.0

returns the url of the archive

v 3.0
(defn url
  ([archive]
   (protocol.archive/-url archive)))
link
(url (open "test-scratch/hello.jar")) => (str (fs/path "test-scratch/hello.jar"))

write ^

[archive entry stream]
Added 3.0

writes files to an archive

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")

zip-system? ^

[obj]
Added 3.0

checks if object is a `ZipSystem`

v 3.0
(defn zip-system?
  ([obj]
   (= ZipFileSystem (type obj))))
link
(zip-system? (open "test-scratch/hello.jar")) => true


*defaults* ^

NONE
(def ^:dynamic *defaults*)
link

*filewatchers* ^

NONE
(defonce ^:dynamic *filewatchers* (atom {}))
link

add-io-watch ^

[obj k f opts]
Added 3.0

registers the watch to a global list of *filewatchers*

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

event-lookup ^

NONE
(def event-lookup (zipmap event-types event-kinds))
link

event-types ^

NONE
(def event-types  [:create :delete :modify])
link

kind-lookup ^

NONE
(def kind-lookup  (zipmap event-kinds event-types))
link

list-io-watch ^

[obj _]
Added 3.0

list all *filewatchers

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?})

pattern ^

[s]
Added 3.0

creates a regex pattern from the string representation

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"

process-event ^

[watcher kind file]
Added 3.0

helper function to process event

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"]

register-entry ^

[service path]
Added 3.0

adds a path to the watch service

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]
Added 3.0

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]
Added 3.0

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"))})

remove-io-watch ^

[obj k _]
Added 3.0

removes the watcher with the given key

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)) => {}

run-watcher ^

[watcher]
Added 3.0

initiates the watcher with the given callbacks

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"]

start-watcher ^

[watcher]
Added 3.0

starts the watcher

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

stop-watcher ^

[watcher]
Added 3.0

stops the watcher

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

watch-callback ^

[f root k]
Added 3.0

helper function to create watch callback

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"]

watcher ^

[paths callback options]
Added 3.0

the watch interface provided for java.io.File

v 3.0
(defn watcher
  ([paths callback options]
   (let [paths   (if (coll? paths) paths [paths])]
     (Watcher. paths callback
               (merge *defaults* options)))))
link
(watcher ["src"] (fn [_ _]) {:recursive false}) => (contains {:paths ["src"] :options (contains {:recursive false})})

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 From std.fs.path: All path manipulation functions (path, file-name, parent, exists?, directory?, file?, input-stream, output-stream, etc.).n From std.fs.attribute: attributes, set-attributes.n* From std.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").n file->ns: Converts a file path string to a Clojure namespace string.n read-code: Reads a Clojure source file and returns a lazy sequence of its top-level forms.n get-namespace: Extracts the namespace symbol from a sequence of Clojure forms.n file-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.n create-symlink: Creates a symbolic link.n create-tmpfile: Creates a temporary file.n create-tmpdir: Creates a temporary directory.n select: Selects files and directories within a root path, with options for filtering.n list: Lists files and attributes for a given directory, with options for recursion.n copy: Copies files and directories from a source to a target, preserving directory structure.n copy-single: Copies a single file.n copy-into: Copies selected files from a source into a target directory.n delete: Deletes files and directories, with options for filtering.n move: 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:

  • IArchive Protocol: Defines methods for interacting with archive filesystems (e.g., url, path, list, has?, archive, extract, insert, remove, write, stream).n* ZipFileSystem: Extends the IArchive protocol to work with Java's ZipFileSystem.

Key Functions:

  • zip-system?: Checks if an object is a ZipFileSystem.n create: Creates a new ZIP/JAR archive.n open: Opens an existing archive or creates a new one.n open-and: Opens an archive, performs an operation, and then closes it.n url: Returns the URL of an archive.n path: Returns a Path object for an entry within an archive.n list: Lists all entries in an archive.n has?: Checks if an archive contains a specific entry.n archive: Adds files to an archive.n extract: Extracts entries from an archive.n insert: Inserts a file into an archive.n remove: Removes an entry from an archive.n write: Writes a stream to an entry within an archive.n* stream: Creates an InputStream for 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").n to-mode-number: Converts string file modes to numeric representations.n to-permissions: Converts numeric file modes to a set of PosixFilePermission keywords.n from-permissions: Converts a set of PosixFilePermission keywords to numeric file modes.n owner: Returns the owner of a file.n lookup-owner: Looks up a user principal by name.n set-owner: Sets the owner of a file.n lookup-group: Looks up a group principal by name.n attr: Creates a FileAttribute object.n attr-value: Adjusts attribute values for input (e.g., converting owner name to UserPrincipal).n map->attr-array: Converts a Clojure map of attributes to a FileAttribute array.n attrs->map: Converts a map of FileAttribute objects to a Clojure map.n attributes: 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*: Default LinkOption for not following symbolic links.n *system*: Detects the operating system type (:dos or :unix).n option: Retrieves java.nio.file.CopyOption, StandardOpenOption, etc., by keyword.n pattern: Converts a glob-like string (e.g., *.clj) to a java.util.regex.Pattern.n tag-filter: Adds a :tag to a filter map.n characterise-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.Path to clojure.java.io/IOFactory, allowing Path objects to be used directly with clojure.java.io/input-stream and clojure.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.n path: Creates a java.nio.file.Path object from various inputs (string, vector, URI, URL, File).n path?: Checks if an object is a Path.n section: Creates a Path object without normalization.n to-file: Converts a Path to a java.io.File.n file-name: Returns the last element of a path.n file-system: Returns the FileSystem for a path.n nth-segment, segment-count: Accesses path segments.n parent: Returns the parent path.n root: Returns the root path.n relativize: Creates a relative path between two paths.n subpath: Returns a sub-sequence of path segments.n file-suffix: Returns the file extension as a keyword.n directory?, executable?, set-executable, permissions, typestring, exists?, hidden?, file?, link?, readable?, writable?, empty-directory?: Predicates and functions for querying file/directory properties.n input-stream, output-stream: Opens InputStream or OutputStream for a path.n read-all-lines, read-all-bytes: Reads file content.n file: Returns a java.io.File object.n last-modified: Returns the last modified timestamp.n write-into: Writes an InputStream to 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's java.nio.file.FileVisitor interface.n Filter System: Supports include and exclude filters based on patterns or custom functions.n Hooks: Allows defining pre and post hooks for directories, and a file hook for files.

Key Functions:

  • match-single: Matches a single filter against a path.n match-filter: Matches a path against multiple include/exclude filters.n visit-directory-pre, visit-directory-post, visit-file, visit-file-failed: Helper functions for FileVisitor callbacks.n visitor: Constructs a FileVisitor instance.n walk: 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's java.nio.file.WatchService for event notification.n Watcher record: Encapsulates the state of a filesystem watcher.n Event Kinds: Supports ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY events.n* Filters: Allows filtering events by filename patterns.

Key Functions:

  • pattern: Creates a regex pattern from a glob-like string.n register-entry: Registers a path with a WatchService.n register-sub-directory: Recursively registers subdirectories for watching.n register-path: Registers a file or directory for watching.n process-event: Processes a single watch event.n run-watcher: The main loop for processing watch events.n start-watcher: Starts a filesystem watcher in a separate thread.n stop-watcher: Stops a filesystem watcher.n watcher: Creates a Watcher record.n watch-callback: Creates a callback function for watch events.n add-io-watch, list-io-watch, remove-io-watch: Functions for managing watches on java.io.File objects, extending std.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.