├── src ├── data_readers.clj └── datoteka │ ├── proto.clj │ ├── io.clj │ └── fs.clj ├── scripts └── repl ├── doc └── Makefile ├── .gitignore ├── doc.clj ├── deps.edn ├── test ├── user.clj └── datoteka │ └── tests │ └── test_core.clj ├── LICENSE ├── CHANGES.md └── README.md /src/data_readers.clj: -------------------------------------------------------------------------------- 1 | {java.nio/path datoteka.fs/path 2 | java.io/file datoteka.fs/file} 3 | -------------------------------------------------------------------------------- /scripts/repl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ex 3 | 4 | clojure -J-Xms128m -J-Xmx128m -M:dev:repl -m rebel-readline.main 5 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | all: doc 2 | 3 | doc: 4 | mkdir -p dist/latest/ 5 | cd ..; clojure -A:dev:codox -M doc.clj; 6 | 7 | github: doc 8 | ghp-import -m "Generate documentation" -b gh-pages dist/ 9 | git push origin gh-pages 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml.asc 5 | *.jar 6 | *.class 7 | *.swp 8 | /.lein-* 9 | /.nrepl-port 10 | /doc/index.html 11 | /doc/api 12 | /doc/dist 13 | \#*\# 14 | *~ 15 | .\#* 16 | /.nrepl-history 17 | /*-init.clj 18 | /.cpcache 19 | /.rebel_readline_history 20 | /settings.xml -------------------------------------------------------------------------------- /doc.clj: -------------------------------------------------------------------------------- 1 | (require '[codox.main :as codox]) 2 | 3 | (codox/generate-docs 4 | {:output-path "doc/dist/latest" 5 | :metadata {:doc/format :markdown} 6 | :language :clojure 7 | :name "funcool/datoteka" 8 | :themes [:rdash] 9 | :source-paths ["src"] 10 | :namespaces [#"^datoteka\."] 11 | :source-uri "https://github.com/funcool/datoteka/blob/master/{filepath}#L{line}"}) 12 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps 2 | {commons-io/commons-io {:mvn/version "2.16.1"}} 3 | :paths ["src"] 4 | :aliases 5 | {:dev 6 | {:extra-paths ["test"] 7 | :extra-deps 8 | {org.clojure/clojure {:mvn/version "1.12.0-beta1"} 9 | org.clojure/tools.namespace {:mvn/version "RELEASE"} 10 | criterium/criterium {:mvn/version "RELEASE"} 11 | funcool/cuerdas {:mvn/version "RELEASE"}}} 12 | 13 | :build 14 | {:extra-deps {io.github.clojure/tools.build {:git/tag "v0.10.5" :git/sha "2a21b7a"}} 15 | :ns-default build} 16 | 17 | :codox 18 | {:extra-deps 19 | {codox/codox {:mvn/version "RELEASE"} 20 | org.clojure/tools.reader {:mvn/version "RELEASE"} 21 | codox-theme-rdash/codox-theme-rdash {:mvn/version "RELEASE"}}} 22 | 23 | :repl 24 | {:main-opts ["-m" "rebel-readline.main"] 25 | :extra-deps {com.bhauman/rebel-readline {:mvn/version "RELEASE"}}} 26 | 27 | :outdated 28 | {:extra-deps {com.github.liquidz/antq {:mvn/version "RELEASE"} 29 | org.slf4j/slf4j-nop {:mvn/version "RELEASE"}} 30 | :main-opts ["-m" "antq.core"]}}} 31 | -------------------------------------------------------------------------------- /test/user.clj: -------------------------------------------------------------------------------- 1 | (ns user 2 | (:require 3 | [clojure.pprint :refer [pprint]] 4 | [clojure.test :as test] 5 | [clojure.tools.namespace.repl :as repl] 6 | [clojure.walk :refer [macroexpand-all]] 7 | [criterium.core :as crit] 8 | [datoteka.fs :as fs])) 9 | 10 | (defmacro run-quick-bench 11 | [& exprs] 12 | `(crit/with-progress-reporting (crit/quick-bench (do ~@exprs) :verbose))) 13 | 14 | (defmacro run-quick-bench' 15 | [& exprs] 16 | `(crit/quick-bench (do ~@exprs))) 17 | 18 | (defmacro run-bench 19 | [& exprs] 20 | `(crit/with-progress-reporting (crit/bench (do ~@exprs) :verbose))) 21 | 22 | (defmacro run-bench' 23 | [& exprs] 24 | `(crit/bench (do ~@exprs))) 25 | 26 | ;; --- Development Stuff 27 | 28 | (defn run-test 29 | ([] (run-test #"^datoteka.tests.*")) 30 | ([o] 31 | (repl/refresh) 32 | (cond 33 | (instance? java.util.regex.Pattern o) 34 | (test/run-all-tests o) 35 | 36 | (symbol? o) 37 | (if-let [sns (namespace o)] 38 | (do (require (symbol sns)) 39 | (test/test-vars [(resolve o)])) 40 | (test/test-ns o))))) 41 | 42 | (defn -main 43 | [& args] 44 | (require 'datoteka.tests.test-core) 45 | (let [{:keys [fail]} (test/run-all-tests #"^datoteka.tests.*")] 46 | (if (pos? fail) 47 | (System/exit fail) 48 | (System/exit 0)))) 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2017 Andrey Antukh 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /src/datoteka/proto.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) 2015-2017 Andrey Antukh 2 | ;; All rights reserved. 3 | ;; 4 | ;; Redistribution and use in source and binary forms, with or without 5 | ;; modification, are permitted provided that the following conditions are met: 6 | ;; 7 | ;; * Redistributions of source code must retain the above copyright notice, this 8 | ;; list of conditions and the following disclaimer. 9 | ;; 10 | ;; * Redistributions in binary form must reproduce the above copyright notice, 11 | ;; this list of conditions and the following disclaimer in the documentation 12 | ;; and/or other materials provided with the distribution. 13 | ;; 14 | ;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | ;; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | ;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | ;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | ;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | ;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | ;; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | ;; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | ;; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | ;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | (ns datoteka.proto 26 | "A storage abstraction definition.") 27 | 28 | (defprotocol IUri 29 | (-uri [_] "Coerce to uri.")) 30 | 31 | (defprotocol IPath 32 | (-path [_] "Coerce to path.")) 33 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # Changelog # 2 | 3 | ## Version 4.0 ## 4 | 5 | - Stabilize datoteka.io API: 6 | - Remove `!` from functions for make it similar to clojure.java.io api 7 | - Reorder some parameters for make the api consistent 8 | - Replace syncrhonized Input and Output streams with unsynchronizedd 9 | version from apache commons-io 10 | 11 | ## Version 3.1 ## 12 | 13 | - Add the ability to pass directory to `create-tempfile` and `tempfile` 14 | - Add print-dup impl for File and Path 15 | - Fix reflection warnings 16 | - Change create-dir call signature, it now accepts named options (only 17 | affects if you pass perms) 18 | - Remove `tempfile`, you should be using `create-tempfile` 19 | - Add `delete-on-exit!` helper 20 | - Add `io/coercible?` helper for check if something is implementing 21 | jio/IOFactory protocol 22 | - Add `io/reader` and `io/writer` convencience API. 23 | - Expose `io/bytes-output-stream`, `io/buffered-input-stream` and 24 | `io/buffered-output-stream` (that internally uses commons-io impl of 25 | them that are unsynchronized, more friendly for virtual threads) 26 | - Replace `io/write-to-file!` with more generic `io/write!`. 27 | - Make `io/read-as-bytes` more generic and allow to pass size and offset. 28 | - Add the ability to coerce ByteArrayOutputStream to InputStream for 29 | to be able to read it again. 30 | 31 | ## Version 3.0.64 ## 32 | 33 | Date: 2022-06-22 34 | 35 | - Add `size` helper. 36 | 37 | ## Version 3.0.63 ## 38 | 39 | Date: 2022-06-21 40 | 41 | - Remove `slurp-bytes`; it is inconsistent and does not make sense to have it. 42 | Can be replaced with https://github.com/clj-commons/byte-streams: 43 | Example: `(-> some-path io/input-stream bs/to-byte-array)` 44 | - Return unbuffered input streams on clojure.java.io protocols. 45 | - New helper: `tempfile` returns a tempfile candidate (without creating it). 46 | 47 | 48 | ## Version 2.0.0 ## 49 | 50 | Date: 2021-04-27 51 | 52 | - Code cleaning. 53 | - Remove storage abstractions. 54 | 55 | 56 | ## Version 1.2.0 ## 57 | 58 | Date: 2020-01-10 59 | 60 | - Add proper coersions from Path to File. 61 | - Add proper coersions from File to Path. 62 | - Minor tooling update. 63 | - Update deps. 64 | 65 | 66 | ## Version 1.1.0 ## 67 | 68 | Date: 2019-07-09 69 | 70 | - Dependencies updates. 71 | - Convert to Clojure CLI Tools 72 | 73 | 74 | ## Version 1.0.0 ## 75 | 76 | Date: 2017-02-07 77 | 78 | - First release 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # datoteka # 2 | 3 | A filesystem & path toolset for Clojure 4 | 5 | The purpose of this library: 6 | 7 | - work with paths (creating and manipulating). 8 | - work with the filesystem (files and directories crud and many predicates). 9 | - IO (clojure.java.io implementation for paths and other utilities, 10 | and reexports all existing API for convencience). 11 | 12 | See the [API documentation](https://funcool.github.io/datoteka/latest/) for 13 | more detailed information. 14 | 15 | 16 | ## Install 17 | 18 | ```clojure 19 | funcool/datoteka 20 | {:git/tag "4.0.0" 21 | :git/sha "3372f3a" 22 | :git/url "https://github.com/funcool/datoteka.git"} 23 | ``` 24 | 25 | ## Getting Started 26 | 27 | The path and filesystem helper functions are all exposed under the 28 | `datoteka.fs` namespace, so let's import it: 29 | 30 | ```clojure 31 | (require '[datoteka.fs :as fs]) 32 | ``` 33 | 34 | This library uses JVM NIO, so under the hood, the `java.nio.file.Path` 35 | is used instead of classical `java.io.File`. You have many ways to 36 | create a *path* instance. The basic one is just using the `path` 37 | function: 38 | 39 | ```clojure 40 | (fs/path "/tmp") 41 | ;; => #java.nio/path "/tmp" 42 | ``` 43 | 44 | As you can observe, the path properly prints with a *data reader*, so 45 | once you have imported the library, you can use the `#java.nio/path "/tmp"` 46 | syntax to create paths. 47 | 48 | The paths also can be created from a various kind of objects (such as 49 | URI, URL, String and seq's): 50 | 51 | ```clojure 52 | (fs/path (java.net.URI. "file:///tmp")) 53 | ;; => #java.nio/path "/tmp" 54 | 55 | (fs/path (java.net.URL. "file:///tmp")) 56 | ;; => #java.nio/path "/tmp" 57 | 58 | (fs/path ["/tmp" "foo"]) 59 | ;; => #java.nio/path "/tmp/foo" 60 | ``` 61 | 62 | The `path` function is also variadic, so you can pass multiple 63 | arguments to it: 64 | 65 | ```clojure 66 | (fs/path "/tmp" "foo") 67 | ;; => #java.nio/path "/tmp/foo" 68 | 69 | (fs/path (java.net.URI. "file:///tmp") "foo") 70 | ;; => #java.nio/path "/tmp/foo" 71 | ``` 72 | 73 | And for convenience, you can use the `clojure.java.io` api with paths 74 | in the same way as you have done it with `java.io.File`: 75 | 76 | ```clojure 77 | (require '[clojure.java.io :as io]) 78 | 79 | (io/reader (fs/path "/etc/inputrc")) 80 | ;; => #object[java.io.BufferedReader 0x203dc326 "java.io.BufferedReader@203dc326"] 81 | 82 | (subs (slurp (fs/path "/etc/inputrc")) 0 20) 83 | ;; => "# do not bell on tab" 84 | ``` 85 | 86 | -------------------------------------------------------------------------------- /test/datoteka/tests/test_core.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) 2015-2016 Andrey Antukh 2 | ;; All rights reserved. 3 | ;; 4 | ;; Redistribution and use in source and binary forms, with or without 5 | ;; modification, are permitted provided that the following conditions are met: 6 | ;; 7 | ;; * Redistributions of source code must retain the above copyright notice, this 8 | ;; list of conditions and the following disclaimer. 9 | ;; 10 | ;; * Redistributions in binary form must reproduce the above copyright notice, 11 | ;; this list of conditions and the following disclaimer in the documentation 12 | ;; and/or other materials provided with the distribution. 13 | ;; 14 | ;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | ;; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | ;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | ;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | ;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | ;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | ;; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | ;; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | ;; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | ;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | (ns datoteka.tests.test-core 26 | (:require [clojure.test :as t] 27 | [clojure.java.io :as jio] 28 | [datoteka.io :as io] 29 | [datoteka.fs :as fs])) 30 | 31 | (t/deftest predicates-test 32 | (t/is (fs/path? (fs/path "."))) 33 | 34 | (t/is (fs/absolute? (fs/path "/tmp"))) 35 | (t/is (not (fs/absolute? (fs/path "tmp")))) 36 | 37 | (t/is (fs/executable? "/bin/sh")) 38 | (t/is (not (fs/executable? "/proc/cpuinfo"))) 39 | 40 | (t/is (fs/exists? "/tmp")) 41 | (t/is (not (fs/exists? "/foobar"))) 42 | 43 | (t/is (fs/directory? "/tmp")) 44 | (t/is (not (fs/directory? "/foobar"))) 45 | 46 | (t/is (fs/regular-file? "/proc/cpuinfo")) 47 | (t/is (not (fs/regular-file? "/tmp"))) 48 | 49 | (t/is (fs/hidden? ".bashrc")) 50 | (t/is (not (fs/hidden? "bashrc"))) 51 | 52 | (t/is (fs/readable? "/proc/cpuinfo")) 53 | (t/is (not (fs/readable? "/proc/cpuinfo2"))) 54 | 55 | (t/is (fs/writable? "/tmp")) 56 | (t/is (not (fs/writable? "/proc/cpuinfo"))) 57 | ) 58 | 59 | (t/deftest coercions 60 | (t/is (fs/file? (jio/as-file (fs/path "/tmp/foobar.txt")))) 61 | (t/is (fs/path? (fs/path (jio/as-file "/tmp/foobar.txt"))))) 62 | 63 | (t/deftest path-manipulation-test 64 | (t/is (= (fs/path "/foo") (fs/parent "/foo/bar"))) 65 | (t/is (= "bar.txt" (fs/name "/foo/bar.txt"))) 66 | (t/is (= ["/foo/bar" ".txt"] (fs/split-ext "/foo/bar.txt"))) 67 | (t/is (= fs/*home* (fs/normalize "~"))) 68 | (t/is (= fs/*cwd* (fs/normalize "."))) 69 | (t/is (= (fs/path "/foo/bar") (fs/path "/foo" "bar"))) 70 | (t/is (fs/file? (fs/file "foobar"))) 71 | ) 72 | 73 | (t/deftest io-copy-test-1 74 | (let [input (byte-array [1 2 3 4 5 6 7 8 9 10]) 75 | output (with-open [is (io/bytes-input-stream input)] 76 | (with-open [os (io/bytes-output-stream 10)] 77 | (io/copy is os :size -1) 78 | (with-open [is' (io/input-stream os)] 79 | (io/read is'))))] 80 | 81 | (t/is (= (seq input) 82 | (seq output))))) 83 | 84 | (t/deftest io-copy-test-2 85 | (let [input (byte-array [1 2 3 4 5 6 7 8 9 10]) 86 | output (with-open [is (io/bytes-input-stream input)] 87 | (with-open [os (io/bytes-output-stream 10)] 88 | (io/copy is os :offset 2) 89 | (with-open [is' (io/input-stream os)] 90 | (io/read is'))))] 91 | 92 | (t/is (= (vec output) 93 | [3 4 5 6 7 8 9 10])))) 94 | 95 | (t/deftest read-1 96 | (let [input (byte-array [1 2 3 4 5 6 7 8 9 10]) 97 | output (with-open [input (io/bytes-input-stream input)] 98 | (io/read input))] 99 | 100 | (t/is (= (vec input) 101 | (vec output))))) 102 | 103 | (t/deftest read-2 104 | (let [input (byte-array [1 2 3 4 5 6 7 8 9 10]) 105 | output (with-open [input (io/bytes-input-stream input)] 106 | (io/read input :size 4))] 107 | 108 | (t/is (= (vec output) 109 | [1 2 3 4])))) 110 | 111 | (t/deftest read-3 112 | (let [input (byte-array [1 2 3 4 5 6 7 8 9 10]) 113 | output (with-open [input (io/bytes-input-stream input)] 114 | (io/skip input 4) 115 | (io/read input :size 2))] 116 | (t/is (= (vec output) 117 | [5 6])))) 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /src/datoteka/io.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Andrey Antukh 2 | ;; All rights reserved. 3 | ;; 4 | ;; Redistribution and use in source and binary forms, with or without 5 | ;; modification, are permitted provided that the following conditions are met: 6 | ;; 7 | ;; * Redistributions of source code must retain the above copyright notice, this 8 | ;; list of conditions and the following disclaimer. 9 | ;; 10 | ;; * Redistributions in binary form must reproduce the above copyright notice, 11 | ;; this list of conditions and the following disclaimer in the documentation 12 | ;; and/or other materials provided with the distribution. 13 | ;; 14 | ;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | ;; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | ;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | ;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | ;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | ;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | ;; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | ;; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | ;; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | ;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | (ns datoteka.io 26 | "IO helpers (a wrapper for clojure.java.io namespace)" 27 | (:refer-clojure :exclude [read flush]) 28 | (:require 29 | [clojure.core :as c] 30 | [clojure.java.io :as jio] 31 | [datoteka.fs :as fs] 32 | [datoteka.proto :as pt]) 33 | (:import 34 | java.io.BufferedOutputStream 35 | java.io.ByteArrayInputStream 36 | java.io.ByteArrayOutputStream 37 | java.io.DataInputStream 38 | java.io.DataOutputStream 39 | java.io.InputStream 40 | java.io.InputStreamReader 41 | java.io.OutputStream 42 | java.io.Reader 43 | java.io.Writer 44 | java.lang.AutoCloseable 45 | org.apache.commons.io.IOUtils 46 | org.apache.commons.io.input.BoundedInputStream 47 | org.apache.commons.io.input.BoundedInputStream$Builder 48 | org.apache.commons.io.input.UnsynchronizedBufferedInputStream 49 | org.apache.commons.io.input.UnsynchronizedBufferedInputStream$Builder 50 | org.apache.commons.io.input.UnsynchronizedByteArrayInputStream 51 | org.apache.commons.io.input.UnsynchronizedByteArrayInputStream$Builder 52 | org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream 53 | org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream$Builder)) 54 | 55 | (set! *warn-on-reflection* true) 56 | 57 | (def ^:const default-buffer-size IOUtils/DEFAULT_BUFFER_SIZE) ;; 8 KiB 58 | 59 | (defn coercible? 60 | "Check if the provided object can be coercible to input stream or 61 | output stream. In other workds: checks if it satisfies the 62 | jio/IOFactory protocol." 63 | [o] 64 | (satisfies? jio/IOFactory o)) 65 | 66 | (defn input-stream? 67 | "Check if provided object is an instance of InputStream." 68 | [s] 69 | (instance? InputStream s)) 70 | 71 | (defn output-stream? 72 | "Check if provided object is an instance of OutputStream." 73 | [s] 74 | (instance? OutputStream s)) 75 | 76 | (defn data-input-stream? 77 | "Check if provided object is an instance of DataInputStream." 78 | [s] 79 | (instance? DataInputStream s)) 80 | 81 | (defn data-output-stream? 82 | "Check if provided object is an instance of DataOutputStream." 83 | [s] 84 | (instance? DataOutputStream s)) 85 | 86 | (defn input-stream 87 | "Attempts to coerce its argument into an open java.io.InputStream. 88 | Default implementations always return a java.io.BufferedInputStream. 89 | 90 | Convenciency API, it forwards directly to the 91 | `clojure.java.io/make-input-stream`." 92 | [x & {:as opts}] 93 | (jio/make-input-stream x opts)) 94 | 95 | (defn output-stream 96 | "Attempts to coerce its argument into an open java.io.InputStream. 97 | Default implementations always return a java.io.BufferedInputStream. 98 | 99 | Convenciency API, it forwards directly to the 100 | `clojure.java.io/make-output-stream`." 101 | [x & {:as opts}] 102 | (jio/make-output-stream x opts)) 103 | 104 | (defn reader 105 | "Attempts to coerce its argument into an open java.io.Reader. 106 | Default implementations always return a java.io.BufferedReader. 107 | 108 | Convenciency API, it forwards directly to the 109 | `clojure.java.io/make-reader`." 110 | ^Reader 111 | [x & {:as opts}] 112 | (jio/make-reader x opts)) 113 | 114 | (defn writer 115 | "Attempts to coerce its argument into an open java.io.Writer. 116 | Default implementations always return a java.io.BufferedWriter. 117 | 118 | Convenciency API, it forwards directly to the 119 | `clojure.java.io/make-writer`." 120 | ^Writer 121 | [x & {:as opts}] 122 | (jio/make-writer x opts)) 123 | 124 | (defn resource 125 | "Returns the URL for a named resource. Use the context class loader 126 | if no loader is specified. 127 | Convenciency API, it forwards directly to the 128 | `clojure.java.io/resource`." 129 | (^java.net.URL [x] (jio/resource x)) 130 | (^java.net.URL [x loader] (jio/resource x loader))) 131 | 132 | (defn bytes-input-stream 133 | "Creates an instance of unsyncronized ByteArrayInputStream instance holding the 134 | provided data." 135 | ^InputStream 136 | [^bytes data & {:keys [offset size]}] 137 | (let [builder (doto (UnsynchronizedByteArrayInputStream$Builder.) 138 | (.setByteArray data)) 139 | builder (if offset 140 | (.setOffset builder (int offset)) 141 | builder) 142 | builder (if size 143 | (.setLength builder (int size)) 144 | builder)] 145 | (.get ^UnsynchronizedByteArrayInputStream$Builder builder))) 146 | 147 | (defn bytes-output-stream 148 | "Creates an instance of ByteArrayOutputStream." 149 | ^UnsynchronizedByteArrayOutputStream 150 | [& {:keys [size]}] 151 | (let [builder (UnsynchronizedByteArrayOutputStream$Builder.) 152 | builder (if size 153 | (.setBufferSize builder (int size)) 154 | builder)] 155 | (.get ^UnsynchronizedByteArrayOutputStream$Builder builder))) 156 | 157 | (defn buffered-input-stream 158 | [input & {:keys [buffer-size] :or {buffer-size default-buffer-size}}] 159 | (let [builder (UnsynchronizedBufferedInputStream$Builder.) 160 | builder (.setInputStream ^UnsynchronizedBufferedInputStream$Builder builder 161 | ^InputStream input) 162 | builder (.setBufferSize ^UnsynchronizedBufferedInputStream$Builder builder 163 | (int buffer-size))] 164 | (.get ^UnsynchronizedBufferedInputStream$Builder builder))) 165 | 166 | (defn buffered-output-stream 167 | [output & {:keys [buffer-size] :or {buffer-size default-buffer-size}}] 168 | (BufferedOutputStream. ^OutputStream output (int buffer-size))) 169 | 170 | (defn bounded-input-stream 171 | "Creates an instance of InputStream bounded to a specified size." 172 | ^InputStream 173 | [input size & {:keys [propagate-close] :or {propagate-close true}}] 174 | (let [builder (doto (BoundedInputStream$Builder.) 175 | (.setInputStream ^InputStream input) 176 | (.setMaxCount (long size)) 177 | (.setPropagateClose (boolean propagate-close)))] 178 | (.get ^BoundedInputStream$Builder builder))) 179 | 180 | (defn data-input-stream 181 | ^DataInputStream 182 | [input] 183 | (DataInputStream. ^InputStream input)) 184 | 185 | (defn data-output-stream 186 | ^DataOutputStream 187 | [output] 188 | (DataOutputStream. ^OutputStream output)) 189 | 190 | (defn close 191 | "Close any AutoCloseable resource." 192 | [^AutoCloseable stream] 193 | (.close stream)) 194 | 195 | (defn flush 196 | "Flush the OutputStream" 197 | [^OutputStream stream] 198 | (.flush stream)) 199 | 200 | (defn copy 201 | "Efficiently copy data from `src` (should be instance of 202 | InputStream) to the `dst` (which should be instance of 203 | OutputStream). 204 | 205 | You can specify the size for delimit how much bytes should be 206 | written to the `dst`." 207 | [src dst & {:keys [offset size buffer-size] 208 | :or {offset 0 size -1 buffer-size default-buffer-size}}] 209 | (let [^bytes buff (byte-array buffer-size)] 210 | (IOUtils/copyLarge ^InputStream src ^OutputStream dst (long offset) (long size) buff))) 211 | 212 | (defn write 213 | "Writes content from `src` to the `dst`. 214 | 215 | The `dst` argument should be an instance of OutputStream 216 | If size is provided, no more than that bytes will be written to the 217 | `dst`." 218 | [dst content & {:keys [size offset close] :or {close false} :as opts}] 219 | (assert (output-stream? dst) "expected instance of OutputStream for dst") 220 | (try 221 | (cond 222 | (instance? InputStream content) 223 | (copy content dst opts) 224 | 225 | ;; A faster write operation if we already have a byte array 226 | ;; and we don't specify the size. 227 | (and (bytes? content) 228 | (not size) 229 | (not offset)) 230 | (do 231 | (IOUtils/writeChunked ^bytes content ^OutputStream dst) 232 | (alength ^bytes content)) 233 | 234 | (string? content) 235 | (let [encoding (or (:encoding opts) "UTF-8") 236 | data (.getBytes ^String content ^String encoding)] 237 | (write dst data opts)) 238 | 239 | :else 240 | (with-open [^InputStream input (jio/make-input-stream content opts)] 241 | (copy input dst opts))) 242 | 243 | (finally 244 | (flush dst)))) 245 | 246 | (defn write* 247 | "A specialized version of `write` that coerces `dst` to a output 248 | stream and closes it once all content is written" 249 | [dst content & {:as opts}] 250 | (with-open [^OutputStream dst (jio/make-output-stream dst opts)] 251 | (write dst content opts))) 252 | 253 | (defn skip 254 | [input offset] 255 | (IOUtils/skipFully ^InputStream input (long offset))) 256 | 257 | (defn read 258 | "Read all data or specified size input and return a byte array. 259 | The `input` parameter should be instance of InputStream" 260 | [input & {:keys [size]}] 261 | (assert (input-stream? input) "expected InputStream instance for `input`") 262 | (let [input (if size 263 | (bounded-input-stream input size) 264 | input)] 265 | (IOUtils/toByteArray ^InputStream input))) 266 | 267 | (defn read* 268 | "A specialized version of `read` that coerces `input` to an 269 | InputStream and closes it once the data is read." 270 | [input & {:keys [size] :as opts}] 271 | (with-open [^InputStream input (jio/make-input-stream input opts)] 272 | (read input opts))) 273 | 274 | (defn read-to-buffer 275 | "Read all data or specified size input and return a byte array. 276 | The `input` parameter should be instance of InputStream" 277 | [input buffer & {:keys [size offset]}] 278 | (assert (input-stream? input) "expected InputStream instance for `input`") 279 | 280 | (let [size (or size (alength ^bytes buffer)) 281 | offset (or offset 0)] 282 | (IOUtils/read ^InputStream input 283 | ^bytes buffer 284 | (int offset) 285 | (int size)))) 286 | 287 | (extend UnsynchronizedByteArrayOutputStream 288 | jio/IOFactory 289 | (assoc jio/default-streams-impl 290 | :make-input-stream (fn [x opts] (.toInputStream ^UnsynchronizedByteArrayOutputStream x)) 291 | :make-output-stream (fn [x opts] x))) 292 | 293 | (extend ByteArrayOutputStream 294 | jio/IOFactory 295 | (assoc jio/default-streams-impl 296 | :make-input-stream (fn [x opts] 297 | (bytes-input-stream (.toByteArray ^ByteArrayOutputStream x))) 298 | :make-output-stream (fn [x opts] x))) 299 | 300 | (extend UnsynchronizedBufferedInputStream 301 | jio/IOFactory 302 | (assoc jio/default-streams-impl 303 | :make-input-stream (fn [x opts] x) 304 | :make-reader (fn [^InputStream is opts] 305 | (let [encoding (or (:encoding opts) "UTF-8")] 306 | (-> (InputStreamReader. is ^String encoding) 307 | (jio/make-reader opts)))))) 308 | 309 | ;; Replace the default impl for InputStream to return an 310 | ;; unsynchronozed variant of BufferedInputStream. 311 | (extend InputStream 312 | jio/IOFactory 313 | (assoc jio/default-streams-impl 314 | :make-input-stream (fn [x opts] (buffered-input-stream x opts)) 315 | :make-reader (fn [^InputStream is opts] 316 | (let [encoding (or (:encoding opts) "UTF-8")] 317 | (-> (InputStreamReader. is ^String encoding) 318 | (jio/make-reader opts)))))) 319 | -------------------------------------------------------------------------------- /src/datoteka/fs.clj: -------------------------------------------------------------------------------- 1 | ;; Copyright (c) Andrey Antukh 2 | ;; All rights reserved. 3 | ;; 4 | ;; Redistribution and use in source and binary forms, with or without 5 | ;; modification, are permitted provided that the following conditions are met: 6 | ;; 7 | ;; * Redistributions of source code must retain the above copyright notice, this 8 | ;; list of conditions and the following disclaimer. 9 | ;; 10 | ;; * Redistributions in binary form must reproduce the above copyright notice, 11 | ;; this list of conditions and the following disclaimer in the documentation 12 | ;; and/or other materials provided with the distribution. 13 | ;; 14 | ;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | ;; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | ;; IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | ;; DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | ;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | ;; DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | ;; SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | ;; CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | ;; OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | ;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | (ns datoteka.fs 26 | "File System helpers." 27 | (:refer-clojure :exclude [name with-open]) 28 | (:require 29 | [datoteka.proto :as pt] 30 | [clojure.java.io :as jio] 31 | [clojure.spec.alpha :as s] 32 | [clojure.core :as c]) 33 | (:import 34 | java.io.ByteArrayInputStream 35 | java.io.ByteArrayOutputStream 36 | java.io.File 37 | java.io.InputStream 38 | java.io.Writer 39 | java.net.URI 40 | java.net.URL 41 | java.nio.file.CopyOption 42 | java.nio.file.FileVisitResult 43 | java.nio.file.Files 44 | java.nio.file.LinkOption 45 | java.nio.file.OpenOption 46 | java.nio.file.Path 47 | java.nio.file.Paths 48 | java.nio.file.SimpleFileVisitor 49 | java.nio.file.StandardCopyOption 50 | java.nio.file.StandardOpenOption 51 | java.nio.file.attribute.FileAttribute 52 | java.nio.file.attribute.PosixFilePermissions 53 | java.util.UUID)) 54 | 55 | (set! *warn-on-reflection* true) 56 | 57 | (def ^:private empty-string-array 58 | (make-array String 0)) 59 | 60 | (extend-type String 61 | pt/IPath 62 | (-path [v] (Paths/get v empty-string-array))) 63 | 64 | (def ^:dynamic *cwd* (pt/-path (.getCanonicalPath (java.io.File. ".")))) 65 | (def ^:dynamic *sep* (System/getProperty "file.separator")) 66 | (def ^:dynamic *home* (pt/-path (System/getProperty "user.home"))) 67 | (def ^:dynamic *tmp-dir* (pt/-path (System/getProperty "java.io.tmpdir"))) 68 | (def ^:dynamic *os-name* (System/getProperty "os.name")) 69 | (def ^:dynamic *system* 70 | (if (.startsWith ^String *os-name* "Windows") :dos :unix)) 71 | 72 | (defn path 73 | "Create path from string or more than one string." 74 | ([fst] 75 | (pt/-path fst)) 76 | ([fst & more] 77 | (pt/-path (cons fst more)))) 78 | 79 | (def ^:private open-opts-map 80 | {:truncate StandardOpenOption/TRUNCATE_EXISTING 81 | :create StandardOpenOption/CREATE 82 | :append StandardOpenOption/APPEND 83 | :create-new StandardOpenOption/CREATE_NEW 84 | :delete-on-close StandardOpenOption/DELETE_ON_CLOSE 85 | :dsync StandardOpenOption/DSYNC 86 | :read StandardOpenOption/READ 87 | :write StandardOpenOption/WRITE 88 | :sparse StandardOpenOption/SPARSE 89 | :sync StandardOpenOption/SYNC}) 90 | 91 | (def ^:private copy-opts-map 92 | {:atomic StandardCopyOption/ATOMIC_MOVE 93 | :replace StandardCopyOption/REPLACE_EXISTING 94 | :copy-attributes StandardCopyOption/COPY_ATTRIBUTES}) 95 | 96 | (defn- link-opts ^"[Ljava.nio.file.LinkOption;" 97 | [{:keys [nofollow-links]}] 98 | (if nofollow-links 99 | (into-array LinkOption [LinkOption/NOFOLLOW_LINKS]) 100 | (into-array LinkOption []))) 101 | 102 | (defn- interpret-open-opts 103 | [opts] 104 | {:pre [(every? open-opts-map opts)]} 105 | (->> (map open-opts-map opts) 106 | (into-array OpenOption))) 107 | 108 | (defn- interpret-copy-opts 109 | [opts] 110 | {:pre [(every? copy-opts-map opts)]} 111 | (->> (map copy-opts-map opts) 112 | (into-array CopyOption))) 113 | 114 | (defn make-permissions 115 | "Generate a array of `FileAttribute` instances 116 | generated from `rwxr-xr-x` kind of expressions." 117 | [^String expr] 118 | (let [perms (PosixFilePermissions/fromString expr) 119 | attr (PosixFilePermissions/asFileAttribute perms)] 120 | (into-array FileAttribute [attr]))) 121 | 122 | (defn path? 123 | "Return `true` if provided value is an instance of Path." 124 | [v] 125 | (instance? Path v)) 126 | 127 | (defn file? 128 | "Check if `v` is an instance of java.io.File" 129 | [v] 130 | (instance? File v)) 131 | 132 | (defn absolute? 133 | "Checks if the provided path is absolute." 134 | [path] 135 | (let [^Path path (pt/-path path)] 136 | (.isAbsolute path))) 137 | 138 | (defn relative? 139 | "Check if the provided path is relative." 140 | [path] 141 | (not (absolute? path))) 142 | 143 | (defn executable? 144 | "Checks if the provided path is executable." 145 | [path] 146 | (Files/isExecutable ^Path (pt/-path path))) 147 | 148 | (defn exists? 149 | "Check if the provided path exists." 150 | ([path] (exists? path nil)) 151 | ([path params] 152 | (let [^Path path (pt/-path path)] 153 | (Files/exists path (link-opts params))))) 154 | 155 | (defn directory? 156 | "Checks if the provided path is a directory." 157 | ([path] (directory? path nil)) 158 | ([path params] 159 | (let [^Path path (pt/-path path)] 160 | (Files/isDirectory path (link-opts params))))) 161 | 162 | (defn regular-file? 163 | "Checks if the provided path is a plain file." 164 | ([path] (regular-file? path nil)) 165 | ([path params] 166 | (Files/isRegularFile (pt/-path path) (link-opts params)))) 167 | 168 | (defn link? 169 | "Checks if the provided path is a link." 170 | [path] 171 | (Files/isSymbolicLink (pt/-path path))) 172 | 173 | (defn hidden? 174 | "Check if the provided path is hidden." 175 | [path] 176 | (Files/isHidden (pt/-path path))) 177 | 178 | (defn readable? 179 | "Check if the provided path is readable." 180 | [path] 181 | (Files/isReadable (pt/-path path))) 182 | 183 | (defn writable? 184 | "Check if the provided path is writable." 185 | [path] 186 | (Files/isWritable (pt/-path path))) 187 | 188 | (defn size 189 | "Return the file size." 190 | [path] 191 | (-> path pt/-path Files/size)) 192 | 193 | (defn permissions 194 | "Returns the string representation of the 195 | permissions of the provided path." 196 | ([path] (permissions path nil)) 197 | ([path params] 198 | (let [^Path path (pt/-path path)] 199 | (->> (Files/getPosixFilePermissions path (link-opts params)) 200 | (PosixFilePermissions/toString))))) 201 | 202 | (defn last-modified-time 203 | ([path] (last-modified-time path nil)) 204 | ([path params] 205 | (let [^Path path (pt/-path path)] 206 | (Files/getLastModifiedTime path (link-opts params))))) 207 | 208 | (defn real 209 | "Converts f into real path via Path#toRealPath." 210 | ([path] (real path nil)) 211 | ([path params] 212 | (.toRealPath ^Path (pt/-path path) 213 | (link-opts params)))) 214 | 215 | (defn absolute 216 | "Return absolute path." 217 | [path] 218 | (.toAbsolutePath ^Path (pt/-path path))) 219 | 220 | (defn parent 221 | "Get parent path if it exists." 222 | [path] 223 | (.getParent ^Path (pt/-path path))) 224 | 225 | (defn name 226 | "Return a path representing the name of the file or 227 | directory, or null if this path has zero elements." 228 | [path] 229 | (str (.getFileName ^Path (pt/-path path)))) 230 | 231 | (defn split-ext 232 | "Returns a vector of `[^String name ^String extension]`." 233 | [path] 234 | (let [^Path path (pt/-path path) 235 | ^String path-str (.toString path) 236 | i (.lastIndexOf path-str ".")] 237 | (if (pos? i) 238 | [(subs path-str 0 i) 239 | (subs path-str i)] 240 | [path-str nil]))) 241 | 242 | (defn ext 243 | "Return the extension part of a file." 244 | [path] 245 | (some-> (last (split-ext path)) 246 | (subs 1))) 247 | 248 | (defn base 249 | "Return the base part of a file." 250 | [path] 251 | (first (split-ext path))) 252 | 253 | (defn normalize 254 | "Normalize the path." 255 | [path] 256 | (let [^String path (str (.normalize ^Path (pt/-path path)))] 257 | (cond 258 | (= path "~") 259 | (pt/-path *home*) 260 | 261 | (.startsWith path (str "~" *sep*)) 262 | (pt/-path (.replace path "~" ^String (.toString ^Object *home*))) 263 | 264 | (not (.startsWith path *sep*)) 265 | (pt/-path (str *cwd* *sep* path)) 266 | 267 | :else (pt/-path path)))) 268 | 269 | (defn join 270 | "Resolve path on top of an other path." 271 | ([base path] 272 | (let [^Path base (pt/-path base) 273 | ^Path path (pt/-path path)] 274 | (-> (.resolve base path) 275 | (.normalize)))) 276 | ([base path & more] 277 | (reduce join base (cons path more)))) 278 | 279 | (defn relativize 280 | "Returns the relationship between two paths." 281 | [base other] 282 | (.relativize ^Path (pt/-path other) 283 | ^Path (pt/-path base))) 284 | 285 | (defn file 286 | "Create a file instance" 287 | [path] 288 | (cond 289 | (path? path) 290 | (.toFile ^Path path) 291 | 292 | (string? path) 293 | (File. ^String path) 294 | 295 | :else 296 | (file (pt/-path path)))) 297 | 298 | (defn- list-dir-lazy-seq 299 | ([stream] (list-dir-lazy-seq stream (seq stream))) 300 | ([stream s] 301 | (lazy-seq 302 | (let [p1 (first s) 303 | p2 (rest s)] 304 | (if (seq p2) 305 | (cons p1 (list-dir-lazy-seq stream p2)) 306 | (do 307 | (.close ^java.lang.AutoCloseable stream) 308 | (cons p1 nil))))))) 309 | 310 | (defn list-dir 311 | "Return a lazy seq of files and directories found under the provided 312 | directory. The order of files is not guarrantied. 313 | 314 | NOTE: the seq should be fully realized in order to properly release 315 | all acquired resources for this operation. Converting it to vector 316 | is an option for do it." 317 | ([path] 318 | (let [path (pt/-path path) 319 | stream (Files/newDirectoryStream path)] 320 | (list-dir-lazy-seq stream))) 321 | ([path ^String glob] 322 | (let [path (pt/-path path) 323 | stream (Files/newDirectoryStream ^Path path glob)] 324 | (list-dir-lazy-seq stream)))) 325 | 326 | (defn create-dir 327 | "Create a new directory with all its parents if they does not exists." 328 | [path & {:keys [perms] :or {perms "rwxr-xr-x"}}] 329 | (let [path (pt/-path path) 330 | attrs (make-permissions perms)] 331 | (Files/createDirectories ^Path path attrs))) 332 | 333 | (defn- delete-recursive 334 | [^Path path] 335 | (->> (proxy [SimpleFileVisitor] [] 336 | (visitFile [file attrs] 337 | (Files/delete file) 338 | FileVisitResult/CONTINUE) 339 | (postVisitDirectory [dir exc] 340 | (Files/delete dir) 341 | FileVisitResult/CONTINUE)) 342 | (Files/walkFileTree path))) 343 | 344 | (defn delete 345 | "Delete recursiverly a directory or file." 346 | [path] 347 | (let [^Path path (pt/-path path)] 348 | (if (regular-file? path) 349 | (Files/deleteIfExists path) 350 | (delete-recursive path)))) 351 | 352 | (defn move 353 | "Move or rename a file to a target file. 354 | 355 | By default, this method attempts to move the file to the target 356 | file, failing if the target file exists except if the source and 357 | target are the same file, in which case this method has no 358 | effect. If the file is a symbolic link then the symbolic link 359 | itself, not the target of the link, is moved. 360 | 361 | This method may be invoked to move an empty directory. When invoked 362 | to move a directory that is not empty then the directory is moved if 363 | it does not require moving the entries in the directory. For 364 | example, renaming a directory on the same FileStore will usually not 365 | require moving the entries in the directory. When moving a directory 366 | requires that its entries be moved then this method fails (by 367 | throwing an IOException)." 368 | ([src dst] (move src dst #{:atomic :replace})) 369 | ([src dst flags] 370 | (let [^Path src (pt/-path src) 371 | ^Path dst (pt/-path dst) 372 | opts (interpret-copy-opts flags)] 373 | (Files/move src dst opts)))) 374 | 375 | (defn create-tempdir 376 | "Creates a temp directory on the filesystem." 377 | [& {:keys [prefix dir perms] :or {prefix "" dir *tmp-dir*}}] 378 | (let [dir (if (path? dir) dir (pt/-path dir)) 379 | attrs (if (string? perms) 380 | (make-permissions perms) 381 | (make-array FileAttribute 0))] 382 | (Files/createTempDirectory ^Path dir ^String prefix attrs))) 383 | 384 | (defn create-tempfile 385 | "Create a temporal file." 386 | [& {:keys [suffix prefix dir perms]}] 387 | (let [dir (or (some-> dir path) *tmp-dir*) 388 | attrs (if (string? perms) 389 | (make-permissions perms) 390 | (make-permissions "rwxr--r--"))] 391 | (Files/createTempFile dir prefix suffix attrs))) 392 | 393 | (defn delete-on-exit! 394 | [path] 395 | (.deleteOnExit ^File (file path))) 396 | 397 | ;; --- Implementation 398 | 399 | (defmethod print-method Path 400 | [^Path v ^Writer w] 401 | (.write w (str "#java.nio/path \"" (.toString v) "\""))) 402 | 403 | (defmethod print-dup Path 404 | [^Path v ^Writer w] 405 | (print-method v w)) 406 | 407 | (defmethod print-method File 408 | [^File v ^Writer w] 409 | (.write w (str "#java.io/file \"" (.toString v) "\""))) 410 | 411 | (defmethod print-dup File 412 | [^File v ^Writer w] 413 | (print-method v w)) 414 | 415 | (extend-protocol pt/IUri 416 | URI 417 | (-uri [v] v) 418 | 419 | String 420 | (-uri [v] (URI. v))) 421 | 422 | (extend-protocol pt/IPath 423 | Path 424 | (-path [v] v) 425 | 426 | java.io.File 427 | (-path [v] (.toPath v)) 428 | 429 | URI 430 | (-path [v] (Paths/get v)) 431 | 432 | URL 433 | (-path [v] (Paths/get (.toURI v))) 434 | 435 | clojure.lang.Sequential 436 | (-path [v] 437 | (reduce #(.resolve ^Path %1 ^Path %2) 438 | (pt/-path (first v)) 439 | (map pt/-path (rest v))))) 440 | 441 | (extend-protocol jio/Coercions 442 | Path 443 | (as-file [it] (.toFile it)) 444 | (as-url [it] (jio/as-url (.toFile it)))) 445 | 446 | (def ^:private default-read-opts 447 | (interpret-open-opts #{:read})) 448 | 449 | (def ^:private default-write-opts 450 | (interpret-open-opts #{:truncate :create :write})) 451 | 452 | (defn- path->input-stream 453 | [^Path path] 454 | (Files/newInputStream path default-read-opts)) 455 | 456 | (defn- path->output-stream 457 | [^Path path] 458 | (Files/newOutputStream path default-write-opts)) 459 | 460 | (extend-type Path 461 | jio/IOFactory 462 | (make-reader [path opts] 463 | (let [^InputStream is (path->input-stream path)] 464 | (jio/make-reader is opts))) 465 | (make-writer [path opts] 466 | (let [^OutputStream os (path->output-stream path)] 467 | (jio/make-writer os opts))) 468 | (make-input-stream [path opts] 469 | (jio/make-input-stream (path->input-stream path) opts)) 470 | (make-output-stream [path opts] 471 | (jio/make-output-stream (path->output-stream path) opts))) 472 | 473 | ;; SPEC 474 | 475 | (letfn [(conformer-fn [s] 476 | (cond 477 | (path? s) s 478 | (string? s) (pt/-path s) 479 | :else ::s/invalid)) 480 | (unformer-fn [s] 481 | (str s))] 482 | (s/def ::path (s/conformer conformer-fn unformer-fn))) 483 | 484 | 485 | 486 | 487 | --------------------------------------------------------------------------------