diff --git a/.gitignore b/.gitignore index 0b4fa26..27e7fee 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,9 @@ *.jar lib classes -pom.xml README.html docs -target -pom.xml* +src/raynes/fs/target doc -test/me/raynes/testfiles/round* \ No newline at end of file +test/me/raynes/testfiles/round* +/.idea \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..167ff69 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +- repo: local + hooks: + - id: pom_generator + name: pom_generator + entry: pom_generator.sh + language: script + pass_filenames: false \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index b9d0e4d..0000000 --- a/.travis.yml +++ /dev/null @@ -1,3 +0,0 @@ -language: clojure -lein: lein2 -script: lein2 midje \ No newline at end of file diff --git a/README.markdown b/README.md similarity index 66% rename from README.markdown rename to README.md index ea4654f..866d6bc 100644 --- a/README.markdown +++ b/README.md @@ -1,12 +1,17 @@ # fs - File system utilities for Clojure -[![Build Status](https://secure.travis-ci.org/Raynes/fs.png)](http://travis-ci.org/Raynes/fs) - -[API docs](http://raynes.github.com/fs/) +```clj +[fs "1.4.7"] +``` This library defines some utilities for working with the file system in Clojure. Mostly, it wants to fill the gap that `clojure.java.io` leaves and add on (and prettify) what `java.io.File` provides. +## pre-commit + +- Install: https://pre-commit.com/ +- running locally: This will also happen automatically before committing to a branch, but you can also run the tasks with `pre-commit run --all-files` + ## Usage This library is simple. It is just a collection of functions that do things with the file system. The one thing @@ -22,33 +27,6 @@ because I probably want it. Make sure you include tests. Also, make sure they pa fs is *not* an I/O utility library. We should try to keep things limited to file system activities. -## Artifacts - -Library artifacts are [released to Clojars](https://clojars.org/me.raynes/fs). If you are using Maven, add the following repository -definition to your `pom.xml`: - -``` xml - - clojars.org - http://clojars.org/repo - -``` - -### The Most Recent Release - -With Leiningen: - - [me.raynes/fs "1.4.6"] - - -With Maven: - - - me.raynes - fs - 1.4.6 - - ## License Copyright (C) 2010-2013 Miki Tebeka, Anthony Grimes diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9ed976c --- /dev/null +++ b/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + com.farmlogs + fs + jar + 1.4.7-SNAPSHOT + fs + File system utilities for clojure + https://github.com/FarmLogs/fs + + + Eclipse Public License - v 1.0 + http://www.eclipse.org/legal/epl-v10.html + + + + https://github.com/FarmLogs/fs + scm:git:git://github.com/FarmLogs/fs.git + scm:git:ssh://git@github.com/FarmLogs/fs.git + 63d694889a981854aa7ec35f42bd69ac7004591f + + + src + test + + + resources + + + + + resources + + + target + target/classes + + + + + central + https://repo1.maven.org/maven2/ + + false + + + true + + + + clojars + https://repo.clojars.org/ + + true + + + true + + + + releases + s3p://fl-maven-repo/mvn + + true + + + true + + + + + + + + + org.clojure + clojure + 1.11.1 + + + org.apache.commons + commons-compress + 1.21 + + + + + diff --git a/pom_generator.sh b/pom_generator.sh new file mode 100755 index 0000000..d0d0956 --- /dev/null +++ b/pom_generator.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +lein pom +env -i git add pom.xml \ No newline at end of file diff --git a/project.clj b/project.clj index a1170c1..1508693 100644 --- a/project.clj +++ b/project.clj @@ -1,14 +1,33 @@ -(defproject me.raynes/fs "1.4.6" +(defproject me.raynes/fs "1.4.7" :description "File system utilities for clojure" :license {:name "Eclipse Public License - v 1.0" :url "http://www.eclipse.org/legal/epl-v10.html"} - :url "https://github.com/Raynes/fs" - :dependencies [[org.clojure/clojure "1.4.0"] - [org.apache.commons/commons-compress "1.8"]] - :plugins [[lein-midje "3.1.3"] + :url "https://github.com/FarmLogs/fs" + :min-lein-version "2.0.0" + :plugins [[lein-file-replace "0.1.0"] + [s3-wagon-private "1.3.5"] + [lein-midje "3.1.3"] [codox "0.8.10"]] - :codox {:src-dir-uri "https://github.com/Raynes/fs/blob/master/" - :src-linenum-anchor-prefix "L" - :defaults {:doc/format :markdown}} - :deploy-repositories {"releases" :clojars} - :profiles {:dev {:dependencies [[midje "1.6.3"]]}}) + :profiles {:dev {:dependencies [[midje "1.6.3"]]}} + :repositories {"releases" {:url "s3p://fl-maven-repo/mvn" + :username :env/AMAZON_KEY + :passphrase :env/AMAZON_SECRET + :sign-releases false}} + :dependencies [[org.clojure/clojure "1.4.0"] + [org.apache.commons/commons-compress "1.21"] + [org.tukaani/xz "1.9"]] + :test-selectors {:default (complement :integration) + :integration :integration + :third-party :third-party + :docker (complement :third-party) + :all (constantly true)} + + :release-tasks [["vcs" "assert-committed"] + ["change" "version" + "leiningen.release/bump-version" "release"] + ["vcs" "commit"] + ["vcs" "tag" "--no-sign"] + ["deploy"] + ["file-replace" "README.md" "\\[api \"" "\"]" "version"] + ["change" "version" "leiningen.release/bump-version"] + ["vcs" "commit"]]) diff --git a/src/me/raynes/fs.clj b/src/me/raynes/fs.clj index cd20a13..d3a7802 100644 --- a/src/me/raynes/fs.clj +++ b/src/me/raynes/fs.clj @@ -5,7 +5,9 @@ [clojure.java.io :as io] [clojure.string :as string] [clojure.java.shell :as sh]) - (:import [java.io File FilenameFilter])) + (:import [java.io File FilenameFilter] + [java.nio.file Files Path] + [java.nio.file.attribute FileAttribute])) ;; Once you've started a JVM, that JVM's working directory is set in stone ;; and cannot be changed. This library will provide a way to simulate a @@ -22,21 +24,20 @@ (let [homedir (io/file (System/getProperty "user.home")) usersdir (.getParent homedir)] (defn home - "With no arguments, returns the current value of the `user.home` system - property. If a `user` is passed, returns that user's home directory. It - is naively assumed to be a directory with the same name as the `user` - located relative to the parent of the current value of `user.home`." + "With no arguments, returns the current value of the user.home system + property. If a user is passed, returns that user's home directory. It + is naively assumed to be a directory with the same name as the user + located relative to the parent of the current value of user.home." ([] homedir) ([user] (if (empty? user) homedir (io/file usersdir user))))) (defn expand-home - "If `path` begins with a tilde (`~`), expand the tilde to the value - of the `user.home` system property. If the `path` begins with a - tilde immediately followed by some characters, they are assumed to - be a username. This is expanded to the path to that user's home - directory. This is (naively) assumed to be a directory with the same - name as the user relative to the parent of the current value of - `user.home`." + "If path begins with a tilde (~), expand the tilde to the value + of the user.home system property. If the path begins with a tilde + immediately followed by some characters, they are assumed to be a + username. This is expanded to the path to that user's home directory. + This is (naively) assumed to be a directory with the same name as the + user relative to the parent of the current value of user.home." [path] (let [path (str path)] (if (.startsWith path "~") @@ -44,15 +45,15 @@ (if (neg? sep) (home (subs path 1)) (io/file (home (subs path 1 sep)) (subs path (inc sep))))) - (io/file path)))) + path))) ;; Library functions will call this function on paths/files so that ;; we get the cwd effect on them. (defn ^File file - "If `path` is a period, replaces it with cwd and creates a new File object - out of it and `paths`. Or, if the resulting File object does not constitute + "If path is a period, replaces it with cwd and creates a new File object + out of it and paths. Or, if the resulting File object does not constitute an absolute path, makes it absolutely by creating a new File object out of - the `paths` and cwd." + the paths and cwd." [path & paths] (when-let [path (apply io/file (if (= path ".") @@ -63,61 +64,61 @@ path (io/file *cwd* path)))) +(extend-protocol clojure.java.io/Coercions + Path + (as-file [this] (.toFile this)) + (as-url [this] (. this (toFile) (toUrl)))) + (defn list-dir - "List files and directories under `path`." + "List files and directories under path." [path] - (seq (.listFiles (file path)))) - -(defmacro ^:private predicate [s path] - `(if ~path - (. ~path ~s) - false)) + (seq (.list (file path)))) (defn absolute? - "Return true if `path` is absolute." + "Return true if path is absolute." [path] - (predicate isAbsolute (io/file path))) + (.isAbsolute (io/file path))) (defn executable? - "Return true if `path` is executable." + "Return true if path is executable." [path] - (predicate canExecute (file path))) + (.canExecute (file path))) (defn readable? - "Return true if `path` is readable." + "Return true if path is readable." [path] - (predicate canRead (file path))) + (.canRead (file path))) (defn writeable? - "Return true if `path` is writeable." + "Return true if path is writeable." [path] - (predicate canWrite (file path))) + (.canWrite (file path))) (defn delete - "Delete `path`." + "Delete path." [path] - (predicate delete (file path))) + (.delete (file path))) (defn exists? - "Return true if `path` exists." + "Return true if path exists." [path] - (predicate exists (file path))) + (.exists (file path))) -(defn absolute - "Return absolute file." +(defn absolute-path + "Return absolute path." [path] - (.getAbsoluteFile (file path))) + (.getAbsolutePath (file path))) -(defn normalized - "Return normalized (canonical) file." +(defn normalized-path + "Return normalized (canonical) path." [path] (.getCanonicalFile (file path))) (defn ^String base-name - "Return the base name (final segment/file part) of a `path`. + "Return the base name (final segment/file part) of a path. - If optional `trim-ext` is a string and the `path` ends with that - string, it is trimmed. + If optional `trim-ext` is a string and the path ends with that string, + it is trimmed. If `trim-ext` is true, any extension is trimmed." ([path] (.getName (file path))) @@ -131,107 +132,45 @@ :else base)))) (defn directory? - "Return true if `path` is a directory." + "Return true if path is a directory." [path] - (predicate isDirectory (file path))) + (.isDirectory (file path))) (defn file? - "Return true if `path` is a file." + "Return true if path is a file." [path] - (predicate isFile (file path))) + (.isFile (file path))) (defn ^Boolean hidden? - "Return true if `path` is hidden." + "Return true if path is hidden." [path] - (predicate isHidden (file path))) + (.isHidden (file path))) -(defn delete-dir - "Delete a directory tree." - [root] - (when (directory? root) - (doseq [path (.listFiles (file root))] - (delete-dir path))) - (delete root)) +(defn- ^Path as-path + "Convert path to a java.nio.file.Path." + [path] + (.toPath (file path))) + +(defn ^Boolean link? + "Return true if path is a link." + [path] + (Files/isSymbolicLink (as-path path))) + +(defn ^File link + "Create a \"hard\" link from path to target." + [path target] + (file (Files/createLink (as-path path) (as-path target)))) -(defmacro ^:private include-java-7-fns [] - (when (try (import '[java.nio.file Files Path LinkOption CopyOption] - '[java.nio.file.attribute FileAttribute]) - (catch Exception _ nil)) - - '(do - (extend-protocol io/Coercions - Path - (as-file [this] (.toFile this)) - (as-url [this] (.. this (toFile) (toURL)))) - - (defn- ^Path as-path - "Convert `path` to a `java.nio.file.Path`. - Requires Java version 7 or greater." - [path] - (.toPath (file path))) - - (defn ^Boolean link? - "Return true if `path` is a link. - Requires Java version 7 or greater." - [path] - (Files/isSymbolicLink (as-path path))) - - (defn ^File link - "Create a \"hard\" link from path to target. - Requires Java version 7 or greater. The arguments - are in the opposite order from the link(2) system - call." - [new-file existing-file] - (file (Files/createLink (as-path new-file) (as-path existing-file)))) - - (defn ^File sym-link - "Create a \"soft\" link from `path` to `target`. - Requires Java version 7 or greater." - [path target] - (file (Files/createSymbolicLink - (as-path path) - (as-path target) - (make-array FileAttribute 0)))) - - (defn ^File read-sym-link - "Return the target of a 'soft' link. - Requires Java version 7 or greater." - [path] - (file (Files/readSymbolicLink (as-path path)))) - - ;; Rewrite directory? and delete-dir to include LinkOptions. - (defn directory? - "Return true if `path` is a directory, false otherwise. - Optional - [link-options](http://docs.oracle.com/javase/7/docs/api/java/nio/file/LinkOption.html) - may be provided to determine whether or not to follow symbolic - links." - [path & link-options] - (Files/isDirectory (as-path path) - (into-array LinkOption link-options))) - - (defn delete-dir - "Delete a directory tree. Optional - [link-options](http://docs.oracle.com/javase/7/docs/api/java/nio/file/LinkOption.html) - may be provided to determine whether or not to follow symbolic - links." - [root & link-options] - (when (apply directory? root link-options) - (doseq [path (.listFiles (file root))] - (apply delete-dir path link-options))) - (delete root)) - - (defn move - "Move or rename a file to a target file. Requires Java version 7 or greater. Optional - [copy-options](http://docs.oracle.com/javase/7/docs/api/java/nio/file/CopyOption.html) - may be provided." - [source target & copy-options] - (Files/move (as-path source) (as-path target) (into-array CopyOption copy-options)))))) - -(include-java-7-fns) +(defn ^File sym-link + "Create a \"soft\" link from path to target." + [path target] + (file (Files/createSymbolicLink + (as-path path) + (as-path target) + (make-array FileAttribute 0)))) (defn split-ext - "Returns a vector of `[name extension]`." + "Returns a vector of [name extension]." [path] (let [base (base-name path) i (.lastIndexOf base ".")] @@ -272,11 +211,11 @@ [path] (.mkdirs (file path))) -(def ^{:doc "The root of a unix system is `/`, `nil` on Windows"} +(def ^{:doc "The root of a unix system is /, nil on Windows"} unix-root (when (= File/separator "/") File/separator)) (defn split - "Split `path` to components." + "Split path to components." [path] (let [pathstr (str path) jregx (str "\\Q" File/separator "\\E")] @@ -287,7 +226,7 @@ :else (seq (.split pathstr jregx))))) (defn rename - "Rename `old-path` to `new-path`. Only works on files." + "Rename old-path to new-path. Only works on files." [old-path new-path] (.renameTo (file old-path) (file new-path))) @@ -301,21 +240,21 @@ (throw (IllegalArgumentException. (str path " not found"))))) (defn copy - "Copy a file from `from` to `to`. Return `to`." + "Copy a file from 'from' to 'to'. Return 'to'." [from to] (assert-exists from) (io/copy (file from) (file to)) to) (defn tmpdir - "The temporary file directory looked up via the `java.io.tmpdir` + "The temporary file directory looked up via the java.io.tmpdir system property. Does not create a temporary directory." [] (System/getProperty "java.io.tmpdir")) (defn temp-name - "Create a temporary file name like what is created for [[temp-file]] - and [[temp-dir]]." + "Create a temporary file name like what is created for temp-file + and temp-dir." ([prefix] (temp-name prefix "")) ([prefix suffix] (format "%s%s-%s%s" prefix (System/currentTimeMillis) @@ -323,12 +262,12 @@ (defn- temp-create "Create a temporary file or dir, trying n times before giving up." - [prefix suffix tries f] - (let [tmp (file (tmpdir) (temp-name prefix suffix))] - (when (pos? tries) - (if (f tmp) - tmp - (recur prefix suffix (dec tries) f))))) + ([prefix suffix tries f] + (loop [tries (range tries)] + (let [tmp (file (tmpdir) (temp-name prefix suffix))] + (if (and (seq tries) (f tmp)) + tmp + (recur (rest tries))))))) (defn temp-file "Create a temporary file. Returns nil if file could not be created @@ -342,25 +281,7 @@ even after n tries (default 10)." ([prefix] (temp-dir prefix "" 10)) ([prefix suffix] (temp-dir prefix suffix 10)) - ([prefix suffix tries] (temp-create prefix suffix tries mkdirs))) - -(defn ephemeral-file - "Create an ephemeral file (will be deleted on JVM exit). - Returns nil if file could not be created even after n tries - (default 10)." - ([prefix] (ephemeral-file prefix "" 10)) - ([prefix suffix] (ephemeral-file prefix suffix 10)) - ([prefix suffix tries] (when-let [created (temp-create prefix suffix tries create)] - (doto created .deleteOnExit)))) - -(defn ephemeral-dir - "Create an ephemeral directory (will be deleted on JVM exit). - Returns nil if dir could not be created even after n tries - (default 10)." - ([prefix] (ephemeral-dir prefix "" 10)) - ([prefix suffix] (ephemeral-dir prefix suffix 10)) - ([prefix suffix tries] (when-let [created (temp-create prefix suffix tries mkdirs)] - (doto created .deleteOnExit)))) + ([prefix suffix tries] (temp-create prefix suffix tries mkdir))) ; Taken from https://github.com/jkk/clj-glob. (thanks Justin!) (defn- glob->regex @@ -425,91 +346,57 @@ [root dirs files])) (defn iterate-dir - "Return a sequence `[root dirs files]`, starting from `path` in depth-first order" + "Return a sequence [root dirs files], starting from 'path' in depth-first order" [path] (map walk-map-fn (iterate-dir* path))) (defn walk "Lazily walk depth-first over the directory structure starting at - `path` calling `func` with three arguments `[root dirs files]`. + 'path' calling 'func' with three arguments [root dirs files]. Returns a sequence of the results." [func path] (map #(apply func %) (iterate-dir path))) (defn touch - "Set file modification time (default to now). Returns `path`." + "Set file modification time (default to now). Returns path." [path & [time]] (let [f (file path)] (when-not (create f) (.setLastModified f (or time (System/currentTimeMillis)))) f)) -(defn- char-to-int - [c] - (- (int c) 48)) - -(defn- chmod-octal-digit - [f i user?] - (if (> i 7) - (throw (IllegalArgumentException. "Bad mode")) - (do (.setReadable f (pos? (bit-and i 4)) user?) - (.setWritable f (pos? (bit-and i 2)) user?) - (.setExecutable f (pos? (bit-and i 1)) user?)))) - -(defn- chmod-octal - [mode path] - (let [[user group world] (map char-to-int mode) - f (file path)] - (if (not= group world) - (throw (IllegalArgumentException. - "Bad mode. Group permissions must be equal to world permissions")) - (do (chmod-octal-digit f world false) - (chmod-octal-digit f user true) - path)))) - (defn chmod "Change file permissions. Returns path. - `mode` can be a permissions string in octal or symbolic format. - Symbolic: any combination of `r` (readable) `w` (writable) and - `x` (executable). It should be prefixed with `+` to set or `-` to - unset. And optional prefix of `u` causes the permissions to be set - for the owner only. - Octal: a string of three octal digits representing user, group, and - world permissions. The three bits of each digit signify read, write, - and execute permissions (in order of significance). Note that group - and world permissions must be equal. + 'mode' can be any combination of \"r\" (readable) \"w\" (writable) and \"x\" + (executable). It should be prefixed with \"+\" to set or \"-\" to unset. And + optional prefix of \"u\" causes the permissions to be set for the owner only. Examples: - - ``` - (chmod \"+x\" \"/tmp/foo\") ; Sets executable for everyone - (chmod \"u-wx\" \"/tmp/foo\") ; Unsets owner write and executable - ```" + (chmod \"+x\" \"/tmp/foo\") -> Sets executable for everyone + (chmod \"u-wx\" \"/tmp/foo\") -> Unsets owner write and executable" [mode path] (assert-exists path) - (if (re-matches #"^\d{3}$" mode) - (chmod-octal mode path) - (let [[_ u op permissions] (re-find #"^(u?)([+-])([rwx]{1,3})$" mode)] - (when (nil? op) (throw (IllegalArgumentException. "Bad mode"))) - (let [perm-set (set permissions) - f (file path) - flag (= op "+") - user (not (empty? u))] - (when (perm-set \r) (.setReadable f flag user)) - (when (perm-set \w) (.setWritable f flag user)) - (when (perm-set \x) (.setExecutable f flag user))) - path))) + (let [[_ u op permissions] (re-find #"^(u?)([+-])([rwx]{1,3})$" mode)] + (when (nil? op) (throw (IllegalArgumentException. "Bad mode"))) + (let [perm-set (set permissions) + f (file path) + flag (= op "+") + user (not (empty? u))] + (when (perm-set \r) (.setReadable f flag user)) + (when (perm-set \w) (.setWritable f flag user)) + (when (perm-set \x) (.setExecutable f flag user))) + path)) (defn copy+ - "Copy `src` to `dest`, create directories if needed." + "Copy src to dest, create directories if needed." [src dest] (mkdirs (parent dest)) (copy src dest)) (defn copy-dir - "Copy a directory from `from` to `to`. If `to` already exists, copy the directory - to a directory with the same name as `from` within the `to` directory." + "Copy a directory from 'from' to 'to'. If 'to' already exists, copy the directory + to a directory with the same name as 'from' within the 'to' directory." [from to] (when (exists? from) (if (file? to) @@ -531,15 +418,13 @@ from)) to)))) -(defn copy-dir-into - "Copy directory into another directory if destination already exists." - [from to] - (if-not (exists? to) - (copy-dir from to) - (doseq [file (list-dir from)] - (if (directory? file) - (copy-dir file to) - (copy file (io/file to (base-name file))))))) +(defn delete-dir + "Delete a directory tree." + [root] + (when (directory? root) + (doseq [path (map #(file root %) (.list (file root)))] + (delete-dir path))) + (delete root)) (defn parents "Get all the parent directories of a path." @@ -563,7 +448,7 @@ ".clj"))) (defn path-ns - "Takes a `path` to a Clojure file and constructs a namespace symbol + "Takes a path to a Clojure file and constructs a namespace symbol out of the path." [path] (symbol @@ -572,14 +457,14 @@ (replace \/ \.)))) (defn find-files* - "Find files in `path` by `pred`." + "Find files in path by pred." [path pred] (filter pred (-> path file file-seq))) (defn find-files - "Find files matching given `pattern`." + "Find files matching given pattern." [path pattern] - (find-files* path #(re-matches pattern (.getName ^File %)))) + (find-files* path #(re-matches pattern (.getName %)))) (defn exec "Execute a shell command in the current directory" @@ -587,20 +472,20 @@ (sh/with-sh-dir *cwd* (apply sh/sh body))) (defmacro with-cwd - "Execute `body` with a changed working directory." + "Execute body with a changed working directory." [cwd & body] `(binding [*cwd* (file ~cwd)] ~@body)) (defmacro with-mutable-cwd - "Execute the `body` in a binding with `*cwd*` bound to `*cwd*`. - This allows you to change `*cwd*` with `set!`." + "Execute the body in a binding with *cwd* bound to *cwd*. + This allows you to change *cwd* with set!." [& body] `(binding [*cwd* *cwd*] ~@body)) (defn chdir - "set!s the value of `*cwd*` to `path`. Only works inside of - [[with-mutable-cwd]]" + "set!s the value of *cwd* to path. Only works inside of + with-mutable-cwd" [path] (set! *cwd* (file path))) diff --git a/src/me/raynes/fs/compression.clj b/src/me/raynes/fs/compression.clj index 0bc9a29..329aa6c 100644 --- a/src/me/raynes/fs/compression.clj +++ b/src/me/raynes/fs/compression.clj @@ -6,92 +6,61 @@ (org.apache.commons.compress.archivers.tar TarArchiveInputStream TarArchiveEntry) (org.apache.commons.compress.compressors bzip2.BZip2CompressorInputStream - xz.XZCompressorInputStream) - (java.io ByteArrayOutputStream))) + xz.XZCompressorInputStream))) (defn unzip - "Takes the path to a zipfile `source` and unzips it to target-dir." + "Takes the path to a zipfile source and unzips it to target-dir." ([source] (unzip source (name source))) ([source target-dir] - (with-open [zip (ZipFile. (fs/file source))] - (let [entries (enumeration-seq (.entries zip)) - target-file #(fs/file target-dir (str %))] - (doseq [entry entries :when (not (.isDirectory ^java.util.zip.ZipEntry entry)) - :let [f (target-file entry)]] - (fs/mkdirs (fs/parent f)) - (io/copy (.getInputStream zip entry) f)))) + (let [zip (ZipFile. (fs/file source)) + entries (enumeration-seq (.entries zip)) + target-file #(fs/file target-dir (str %))] + (doseq [entry entries :when (not (.isDirectory ^java.util.zip.ZipEntry entry)) + :let [f (target-file entry)]] + (fs/mkdirs (fs/parent f)) + (io/copy (.getInputStream zip entry) f))) target-dir)) (defn- add-zip-entry "Add a zip entry. Works for strings and byte-arrays." - [^java.util.zip.ZipOutputStream zip-output-stream [^String name content & remain]] + [zip-output-stream [name content & remain]] (.putNextEntry zip-output-stream (java.util.zip.ZipEntry. name)) (if (string? content) ;string and byte-array must have different methods (doto (java.io.PrintStream. zip-output-stream true) (.print content)) - (.write zip-output-stream ^bytes content)) + (.write zip-output-stream content)) (.closeEntry zip-output-stream) (when (seq (drop 1 remain)) (recur zip-output-stream remain))) (defn make-zip-stream "Create zip file(s) stream. You must provide a vector of the - following form: - - ```[[filename1 content1][filename2 content2]...]```. + following form: [[filename1 content1][filename2 content2]...]. You can provide either strings or byte-arrays as content. The piped streams are used to create content on the fly, which means this can be used to make compressed files without even writing them - to disk." - [& filename-content-pairs] + to disk." [& filename-content-pairs] (let [file - (let [pipe-in (java.io.PipedInputStream.) - pipe-out (java.io.PipedOutputStream. pipe-in)] - (future - (with-open [zip (java.util.zip.ZipOutputStream. pipe-out)] - (add-zip-entry zip (flatten filename-content-pairs)))) - pipe-in)] + (let [pipe-in (java.io.PipedInputStream.) + pipe-out (java.io.PipedOutputStream. pipe-in)] + (future + (with-open [zip (java.util.zip.ZipOutputStream. pipe-out)] + (add-zip-entry zip (flatten filename-content-pairs)))) + pipe-in)] (io/input-stream file))) (defn zip "Create zip file(s) on the fly. You must provide a vector of the - following form: - - ```[[filename1 content1][filename2 content2]...]```. + following form: [[filename1 content1][filename2 content2]...]. You can provide either strings or byte-arrays as content." [filename & filename-content-pairs] (io/copy (make-zip-stream filename-content-pairs) (fs/file filename))) -(defn- slurp-bytes [fpath] - (with-open [data (io/input-stream (fs/file fpath))] - (with-open [out (ByteArrayOutputStream.)] - (io/copy data out) - (.toByteArray out)))) - -(defn make-zip-stream-from-files - "Like make-zip-stream but takes a sequential of file paths and builds filename-content-pairs - based on those" - [fpaths] - (let [filename-content-pairs (map (juxt fs/base-name slurp-bytes) fpaths)] - (make-zip-stream filename-content-pairs))) - -(defn zip-files - "Zip files provided in argument vector to a single zip. Converts the argument list: - - ```(fpath1 fpath2...)``` - - into filename-content -pairs, using the original file's basename as the filename in zip`and slurping the content: - - ```([fpath1-basename fpath1-content] [fpath2-basename fpath2-content]...)``" - [filename fpaths] - (io/copy (make-zip-stream-from-files fpaths) - (fs/file filename))) - (defn- tar-entries "Get a lazy-seq of entries in a tarfile." [^TarArchiveInputStream tin] @@ -99,35 +68,31 @@ (cons entry (lazy-seq (tar-entries tin))))) (defn untar - "Takes a tarfile `source` and untars it to `target`." + "Takes a tarfile source and untars it to target." ([source] (untar source (name source))) ([source target] (with-open [tin (TarArchiveInputStream. (io/input-stream (fs/file source)))] (doseq [^TarArchiveEntry entry (tar-entries tin) :when (not (.isDirectory entry)) :let [output-file (fs/file target (.getName entry))]] (fs/mkdirs (fs/parent output-file)) - (io/copy tin output-file) - (when (.isFile entry) - (fs/chmod (apply str (take-last - 3 (format "%05o" (.getMode entry)))) - (.getPath output-file))))))) + (io/copy tin output-file))))) (defn gunzip - "Takes a path to a gzip file `source` and unzips it." + "Takes a path to a gzip file source and unzips it." ([source] (gunzip source (name source))) ([source target] (io/copy (-> source fs/file io/input-stream GZIPInputStream.) (fs/file target)))) (defn bunzip2 - "Takes a path to a bzip2 file `source` and uncompresses it." + "Takes a path to a bzip2 file source and uncompresses it." ([source] (bunzip2 source (name source))) ([source target] (io/copy (-> source fs/file io/input-stream BZip2CompressorInputStream.) (fs/file target)))) (defn unxz - "Takes a path to a xz file `source` and uncompresses it." + "Takes a path to a xz file source and uncompresses it." ([source] (unxz source (name source))) ([source target] (io/copy (-> source fs/file io/input-stream XZCompressorInputStream.)