Date: 27 November 2018
Version: 3.0.2
1 Introduction
hara.io.file aims to provide consistency of reporting for file operations. Operations are designed using the java.nio.file.FileVisitor pattern. File and directory manipulation are considered as bulk operations by default.
1.1 Installation
Add to project.clj
dependencies:
[hara/io.file "3.0.2"]
All functions are in the hara.io.file
namespace.
(use (quote hara.io.file))
2 Operation
copy ^
copies all specified files from one to another
(copy "src" ".src" {:include [".clj"]})
=> map?
(delete ".src")
copy-single ^
copies a single file to a destination
(copy-single "project.clj"
"dev/scratch/project.clj.bak"
{:options #{:replace-existing}})
=> (path "." "dev/scratch/project.clj.bak")
(delete "dev/scratch/project.clj.bak")
create-directory ^
creates a directory on the filesystem
(do (create-directory "dev/scratch/.hello/.world/.foo")
(directory? "dev/scratch/.hello/.world/.foo"))
=> true
(delete "dev/scratch/.hello")
create-symlink ^
creates a symlink to another file
(do (create-symlink "dev/scratch/project.lnk" "project.clj")
(link? "dev/scratch/project.lnk"))
=> true
create-tmpdir ^
creates a temp directory on the filesystem
(create-tmpdir)
;;=> #path:"/var/folders/d6/yrjldmsd4jd1h0nm970wmzl40000gn/T/4870108199331749225"
create-tmpfile ^
creates a tempory file
(create-tmpfile)
;;#file:"/var/folders/rc/4nxjl26j50gffnkgm65ll8gr0000gp/T/tmp2270822955686495575"
=> java.io.File
delete ^
copies all specified files from one to another
(do (copy "src/hara/test.clj" ".src/hara/test.clj")
(delete ".src" {:include ["test.clj"]}))
=> #{(str (path ".src/hara/test.clj"))}
(delete ".src")
=> set?
list ^
lists the files and attributes for a given directory
(list "src")
=> {"/Users/chris/Development/chit/hara/src" "rwxr-xr-x/d",
"/Users/chris/Development/chit/hara/src/hara" "rwxr-xr-x/d"}
(list "../hara/src/hara/io" {:recursive true})
=> {"/Users/chris/Development/chit/hara/src/hara/io" "rwxr-xr-x/d",
"/Users/chris/Development/chit/hara/src/hara/io/file/reader.clj" "rw-r--r--/-",
"/Users/chris/Development/chit/hara/src/hara/io/project.clj" "rw-r--r--/-",
"/Users/chris/Development/chit/hara/src/hara/io/file/filter.clj" "rw-r--r--/-",
... ...
"/Users/chris/Development/chit/hara/src/hara/io/file/path.clj" "rw-r--r--/-",
"/Users/chris/Development/chit/hara/src/hara/io/file/walk.clj" "rw-r--r--/-",
"/Users/chris/Development/chit/hara/src/hara/io/file.clj" "rw-r--r--/-"}
move ^
moves a file or directory
(do (move "shortlist" ".shortlist")
(move ".shortlist" "shortlist"))
(move ".non-existent" ".moved")
=> {}
select ^
selects all the files in a directory
(->> (select "src/hara/io/file")
(map #(relativize "src/hara" %))
(map str)
(sort))
=> ["io/file"
"io/file/attribute.clj"
"io/file/charset.clj"
"io/file/common.clj"
"io/file/filter.clj"
"io/file/option.clj"
"io/file/path.clj"
"io/file/reader.clj"
"io/file/walk.clj"
"io/file/watch.clj"
"io/file/writer.clj"]
3 Path
file-name ^
returns the last section of the path
(str (file-name "src/hara"))
=> "hara"
nth-segment ^
returns the nth segment of a given path
(str (nth-segment "/usr/local/bin" 1))
=> "local"
parent ^
returns the parent of the path
(str (parent "/hello/world.html"))
=> "/hello"
path ^
creates a `java.nio.file.Path object
(path "project.clj")
;;=> #path:"/Users/chris/Development/chit/hara/project.clj"
(path (path "project.clj")) ;; idempotent
;;=> #path:"/Users/chris/Development/chit/hara/project.clj"
(path "~") ;; tilda
;;=> #path:"/Users/chris"
(path "src" "hara/time.clj") ;; multiple arguments
;;=> #path:"/Users/chris/Development/chit/hara/src/hara/time.clj"
(path ["src" "hara" "time.clj"]) ;; vector
;;=> #path:"/Users/chris/Development/chit/hara/src/hara/time.clj"
(path (java.io.File. ;; java.io.File object
"src/hara/time.clj"))
;;=> #path:"/Users/chris/Development/chit/hara/src/hara/time.clj"
(path (java.net.URI. ;; java.net.URI object
"file:///Users/chris/Development/chit/hara/project.clj"))
;;=> #path:"/Users/chris/Development/chit/hara/project.clj"
path? ^
checks to see if the object is of type Path
(path? (path "/home"))
=> true
relativize ^
returns the relationship between two paths
(str (relativize "hello"
"hello/world.html"))
=> "world.html"
root ^
returns the root path
(str (root "/usr/local/bin"))
=> "/"
segment-count ^
returns the number of segments of a given path
(segment-count "/usr/local/bin")
=> 3
subpath ^
returns the subpath of a given path
(str (subpath "/usr/local/bin/hello" 1 3))
=> "local/bin"
4 File
file ^
returns the input as a file
(file "project.clj")
=> java.io.File
file->ns ^
converts a file string to an ns string
(file->ns "hara/io/file_test")
=> "hara.io.file-test"
file-system ^
returns the filesystem governing the path
(file-system ".")
;; #object[sun.nio.fs.MacOSXFileSystem 0x512a9870 "sun.nio.fs.MacOSXFileSystem@512a9870"]
=> java.nio.file.FileSystem
input-stream ^
opens a file as an input-stream
(input-stream "project.clj")
input-stream? ^
checks if object is an input-stream
(input-stream? (input-stream "project.clj"))
ns->file ^
converts an ns string to a file string
(ns->file 'hara.io.file-test)
=> "hara/io/file_test"
option ^
shows all options for file operations
(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 ^
opens a file as an output-stream
(output-stream "project.clj")
output-stream? ^
checks if object is an output-stream
(output-stream? (output-stream "project.clj"))
read-all-bytes ^
opens a file and reads the contents as a byte array
(read-all-bytes "project.clj")
read-all-lines ^
opens a file and reads the contents as an array of lines
(read-all-lines "project.clj")
read-code ^
takes a file and returns a lazy seq of top-level forms
(->> (read-code "src/hara/io/file.clj")
first
(take 2))
=> '(ns hara.io.file)
resource ^
accesses a url on the classpath
(resource "project.clj")
=> java.net.URL
write ^
writes a stream to a path
(-> (java.io.FileInputStream. "project.clj")
(write "project.clj"
{:options #{:replace-existing}}))
write-all-bytes ^
writes a byte-array to file
(write-all-bytes "hello.txt" (.getBytes "Hello World"))
5 Attribute
attributes ^
shows all attributes for a given path
(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
directory? ^
checks whether a file is a directory
(directory? "src")
=> true
(directory? "project.clj")
=> false
empty-directory? ^
checks if a directory is empty, returns true if both are true
(empty-directory? ".")
=> false
executable? ^
checks whether a file is executable
(executable? "project.clj")
=> false
(executable? "/usr/bin/whoami")
=> true
exists? ^
checks whether a file exists
(exists? "project.clj")
=> true
(exists? "NON.EXISTENT")
=> false
file-type ^
encodes the type of file as a keyword
(file-type "hello.clj")
=> :clj
(file-type "hello.java")
=> :java
file? ^
checks whether a file is not a link or directory
(file? "project.clj")
=> true
(file? "src")
=> false
hidden? ^
checks whether a file is hidden
(hidden? ".gitignore")
=> true
(hidden? "project.clj")
=> false
link? ^
checks whether a file is a link
(link? "project.clj")
=> false
(link? (create-symlink "project.lnk"
"project.clj"))
=> true
(delete "project.lnk")
permissions ^
returns the permissions for a given file
(permissions "src")
=> "rwxr-xr-x"
readable? ^
checks whether a file is readable
(readable? "project.clj")
=> true
set-attributes ^
sets all attributes for a given path
(set-attributes "project.clj"
{:owner "chris",
:group "staff",
:permissions "rw-rw-rw-"})
;;=> #path:"/Users/chris/Development/chit/lucidity/project.clj"
shorthand ^
returns the shorthand string for a given entry
(shorthand "src")
=> "d"
(shorthand "project.clj")
=> "-"
writable? ^
checks whether a file is writable
(writable? "project.clj")
=> true
6 Advanced
As all bulk operations are based on hara.io.file.walk/walk
, it provides a consistent interface for working with files:
6.1 depth
The :depth
option determines how far down the directory listing to move
;; listing the src directory to a depth of 2
(list "src" {:depth 2})
=> {"/Users/chris/Development/chit/hara/src" "rwxr--r--/d",
"/Users/chris/Development/chit/hara/src/hara/data.clj" "rw-r--r--/-",
... ...
"/Users/chris/Development/chit/hara/src/hara.function" "rwxr-xr-x/d"}
;; copying the src directory to a depth of 2
(copy "src" ".src" {:depth 2})
;; delete the .src directory to a depth of 2
(delete ".src" {:depth 2})
The :recursive
flag enables walking to all depths
;; lists contents of src directory, :recursive is false by default
(list "src" {:recursive true})
=> {"/Users/chris/Development/chit/hara/src" "rwxr--r--/d",
"/Users/chris/Development/chit/hara/src/hara/data.clj" "rw-r--r--/-",
... ...
"/Users/chris/Development/chit/hara/src/hara.function" "rwxr-xr-x/d"}
;; copying the src directory, :recursive is true by default
(copy "src" ".src" {:recursive false})
=> {"src" ".src",
"src/hara" ".src/hara"}
;; delete the .src directory, :recursive is true by default
(delete ".src" {:recursive false})
=> #{"/Users/chris/Development/chit/hara/.src/hara"
"/Users/chris/Development/chit/hara/.src"}
6.2 simulate
When the :simulate
flag is set, the operation is not performed but will output as if the operation has been done.
(copy "src" ".src" {:simulate true})
=> {"src" ".src",
"src/hara" ".src/hara"}
(move "src" ".src" {:simulate true})
=> {"/Users/chris/Development/chit/hara/src/hara/data.clj"
"/Users/chris/Development/chit/hara/.src/hara/data.clj",
... ...
"/Users/chris/Development/chit/hara/src/hara/time/data/coerce.clj"
"/Users/chris/Development/chit/hara/.src/hara/time/data/coerce.clj"}
(delete ".src" {:simulate true})
=> #{"/Users/chris/Development/chit/hara/.src/hara"
"/Users/chris/Development/chit/hara/.src"}
6.3 filter
Files can be included or excluded through an array of file filters. Values for :exclude
and :include
array elements can be either a pattern or a function:
(select "." {:exclude [".clj"
directory?]
:recursive false})
=> [;; #path:"/Users/chris/Development/chit/hara/.gitignore"
;; #path:"/Users/chris/Development/chit/hara/.gitmodules"
...
;; #path:"/Users/chris/Development/chit/hara/spring.jpg"
;; #path:"/Users/chris/Development/chit/hara/travis.jpg"
]
(select "." {:include [".clj$"
file?]
:recursive false})
=> [;; #path:"/Users/chris/Development/chit/hara/.midje.clj"
;; #path:"/Users/chris/Development/chit/hara/project.clj"
]
:accumulate
is another options to set that specifies which files are going to be included in in accumulation:
(select "." {:accumulate #{}})
=> []
(select "src" {:depth 2
:accumulate #{:directories}})
=> [;; #path:"/Users/chris/Development/chit/hara/src"
;; #path:"/Users/chris/Development/chit/hara/src/hara"
]
(select "src" {:depth 2
:accumulate #{:files}})
=> [;; #path:"/Users/chris/Development/chit/hara/src/hara.core.base.class.clj"
...
;; #path:"/Users/chris/Development/chit/hara/src/hara/time.clj"
;; #path:"/Users/chris/Development/chit/hara/src/hara/zip.clj"
]
6.4 file system
The :file
option takes a function which will run whenever a file is visited:
(select "src" {:include ["/class/"]
:file (fn [{:keys [path]}] (println (str path)))})
;; /Users/chris/Development/chit/hara/src/hara.core.base.class.clj
;; /Users/chris/Development/chit/hara/src/hara.core.base.class/enum.clj
;; /Users/chris/Development/chit/hara/src/hara.core.base.class/inheritance.clj
;; /Users/chris/Development/chit/hara/src/hara.core.base.class/multi.clj
The :directory
option takes either a function or a map of function which will run whenever a directory is visited:
(select "src" {:include ["/class"]
:directory (fn [{:keys [path]}]
(println (str path)))})
;; /Users/chris/Development/chit/hara/src/hara.core.base.class
(select "src" {:include ["/class"]
:directory {:pre (fn [{:keys [path]}]
(println "PRE" (str path)))
:post (fn [{:keys [path]}]
(println "POST " (str path)))}})
;; PRE /Users/chris/Development/chit/hara/src/hara.core.base.class
;; POST /Users/chris/Development/chit/hara/src/hara.core.base.class
:options
are passed in for move
and copy
;; `:replace-existing` replaces an existing file if it exists.
;; `:copy-attributes` copy attributes to the new file.
(copy "project.clj" "project.clj.bak"
{:options [:replace-existing
:copy-attributes]})
;; `:atomic-move` moves the file as an atomic file system operation.
(move "project.clj.bak" "project.clj"
{:options [:replace-existing
:atomic-move]})
:with
is either #{:root}
to include the root path or #{}
to not include the root path. It is set to #{:root}
for copy
, move
and delete
. It is set to #{}
for list
and select
.
6.5 accumulator
:accumulator
sets the atom that contains the accumulated values during the walk:
(let [acc (atom [])]
(select "src/hara.core.base.class" {:accumulator acc})
(select "src/hara/common" {:accumulator acc})
(map #(str (relativize "src/hara" %))
@acc))
=> ("class"
"class/checks.clj"
"class/enum.clj"
"class/inheritance.clj"
"class/multi.clj"
"common"
"common/checks.clj"
"common/error.clj"
"common/hash.clj"
"common/pretty.clj"
"common/primitives.clj"
"common/state.clj"
"common/string.clj"
"common/watch.clj")
For more examples of how it is used, please see the source code for copy
, delete
list
and move
.