【问题标题】:Idiomatic Clojure to copy resources from running jar to outside惯用的 Clojure 将资源从正在运行的 jar 复制到外部
【发布时间】:2015-02-21 11:51:23
【问题描述】:

这似乎是一个经典问题,但我找不到任何关于它的“clojure 方式”。

所以,我在 resources/(leiningen 项目)中有一个 foo/ 目录。当 jar'd/uberjar'd 时,这个 foo/ 目录放在 jar 的根目录下。由于 jar 内的文件在运行时可能物理上不一致,所以不能使用基本的复制功能递归地将目录复制到外部世界。

Java 世界有几种解决方案(例如How to write a Java program which can extract a JAR file and store its data in specified directory (location)?How to extract directory (and sub directories) from jar resource?),但我没有找到任何适用于Clojure 的现有解决方案。 作为初学者(Clojure 和 Java),我不确定如何将上述解决方案转换为 Clojure。从 Java 逐行翻译到 Clojurish Java Interop 似乎并不正确。 有没有一种“官方的”、clojure 惯用的方式来做到这一点?

请注意,我使用的是 Raynes 的 fs 实用程序库。似乎没有直接执行此操作的功能,但也许我可以使用一些元素来简化流程? (除了明显的基本io糖)

【问题讨论】:

    标签: java jar clojure io copy


    【解决方案1】:

    几个月前我写过cpath-clj,它将按URI 列出类路径上的资源。然后您可以尝试以下操作:

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

    由于编写库时没有考虑到这个用例,因此有些杂耍发生:

    • (cp/resources (io/resource "foo")) 将为您提供 您的 foo 目录的内容 - 如果您只使用 (cp/resources "foo") 类路径上的所有此类目录,
    • 理论上,类路径上可以有多个具有相同path的文件,这就是函数返回多个uris的原因;在我们的例子中,只有第一个是感兴趣的。
    • path 总是以斜线开头,所以要获得相对的,我们必须删除它。

    也许这对你有帮助。

    【讨论】:

    • 谢谢!现在无法对其进行测试,但这肯定看起来很有希望,并且是我想出的更清洁的替代方案!
    • 我认为这实际上适合库的范围,因此,如果您找到满意的解决方案,请随时打开 PR,我很乐意将其合并。 :)
    【解决方案2】:
    (defn copy-resource! [resource-filename]
      (let [resource-file (io/resource resource-filename)
            tmp-file (io/file "/tmp" resource-filename)]
        (with-open [in (io/input-stream resource-file)] (io/copy in tmp-file))))
    
    (copy-resource! "my-logo.png")
    

    【讨论】:

      【解决方案3】:

      在尝试了更多并从#clojure 获得帮助后,我能够从我之前的第一个链接中提出“kind-of-clojurish-i-guess”翻译:

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

      我想我可以稍微整理一下(尤其是在 .startsWith 位周围),但至少效果很好。

      【讨论】:

      • 您可以通过将(enumeration-seq (.entries jar)) 替换为(filter #(re-matches from (.getName %)) (enumeration-seq (.entries jar))) 并将from 参数设置为正则表达式来使其更加灵活。然后你可以删除(if (.startsWith ...)
      【解决方案4】:

      我不确定惯用的部分,但可以这样列出资源目录中的文件

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

      而从资源中的目录foo复制一个文件到当前路径就这么简单

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

      结合这两者,您应该能够编写一个递归函数来满足您的要求

      【讨论】:

      • 不幸的是,这就是重点:您不能在正在运行的 jar 中的文件上使用 io/copy(尽管在 repl 或 lein run 中工作),因为 jar 中的资源文件不是处于“文件”状态(因此,如果在 jar 内的“文件”上使用 io/file 将抛出“IllegalArgumentException : not a file”)。它适用于 lein run 和 REPL,因为资源文件不是从 jar 内部调用,而是直接从文件系统调用,因此它处于兼容文件状态。
      • 您能否提供一些有关您为什么需要这样做的信息?我以前见过类似的东西,但这不是一个常见的用例,对于您想要做的事情,可能存在更简单的解决方案
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-18
      • 2012-04-27
      • 2016-12-29
      • 1970-01-01
      • 2011-10-10
      相关资源
      最近更新 更多