Idiomatic Clojure to copy resources from running jar to outside

It seems like a classical problem but I can't find anything about it "the clojure way".

So, I have a foo/ directory inside resources/ (leiningen project). When jar'd/uberjar'd, this foo/ directory is put at the root of the jar. As files inside jar may not be physically consistent at runtime, you can't use basic copy function to recursively copy the directory to the outside world.

Several solutions for the Java world exist (How to write a Java program which can extract a JAR file and store its data in specified directory (location)? and How to extract directory (and sub directories) from jar resource? for example), but I didn't find any existing solution for Clojure. As a beginner (both Clojure and Java), I'm not sure how to translate above solutions to Clojure. Translating literally line by line from Java to Clojurish Java Interop doesn't seem right. Is there an "official", clojure-idiomatic way to do this ?

Note that I'm using Raynes' fs utilities library. There doesn't seem to be a function to do this directly, but maybe there's some elements I can use to simplify the process ? (besides the obvious basic io sugar)


I've written cpath-clj a few months back that will list resources on the classpath by URI. You could then try the following:

(require '[cpath-clj.core :as cp]
         '[clojure.java.io :as io])

(doseq [[path uris] (cp/resources (io/resource "foo"))
        :let [uri (first uris)
              relative-path (subs path 1)
              output-file (io/file output-directory relative-path)]]
  (with-open [in (io/input-stream uri)]
    (io/copy in output-file)))

There is some juggling going on since the library was not written with this use case in mind:

  • (cp/resources (io/resource "foo")) will give you the contents of your foo directory - if you had only used (cp/resources "foo") all such directories on the classpath would have been found,
  • theoretically, there can be multiple files with the same path on the classpath, that's why the function returns multiple uris ; in our case, only the first one is of interest.
  • path is always starting with a slash, so to get the relative one, we have to remove it.
  • Maybe that's helpful to you.


    I am not sure about the idiomatic part but a file in the resource directory can be listed this way

    (defn list-resource
      [resource-dir]
      (let [resource (io/file (io/resource resource-dir))]
        (file-seq resource)))
    

    And copying a file from the directory foo in the resource to the current path is as simple as

    (io/copy (io/file (io/resource "foo/test.txt")) (io/file "test.txt"))
    

    Combining the two you should be able to write a recursive function that does what you ask


    After trying around some more and getting help from #clojure, I was able to came up with a "kind-of-clojurish-i-guess" translation from my previous first link :

    (ns my-ns
      (:require [me.raynes.fs    :as f])
      (:import [java.util.jar JarFile JarEntry]))
    
    (defn extract-dir-from-jar
      "Takes the string path of a jar, a dir name inside that jar and a destination
      dir, and copies the from dir to the to dir."
      [^String jar-dir from to]
      (let [jar (JarFile. jar-dir)]
        (doseq [^JarEntry file (enumeration-seq (.entries jar))]
          (if (.startsWith (.getName file) from)
            (let [f (f/file to (.getName file))]
              (if (.isDirectory file)
                (f/mkdir f)
                (do (f/mkdirs (f/parent f))
                    (with-open [is (.getInputStream jar file)
                                os (io/output-stream f)]
                      (io/copy is os)))))))))
    

    I guess I can tidy this up a bit (especially around the .startsWith bit) but at least that works perfectly.

    链接地址: http://www.djcxy.com/p/83880.html

    上一篇: 没有Dagger2为JUnit测试生成文件

    下一篇: 习惯性的Clojure将资源从运行的jar复制到外部