Importing/using a resource from an external clojar

假装没事ソ 提交于 2020-02-04 15:38:30

问题


What I'm trying to do is package a large file (a MIDI soundfont) in a standalone Maven repo/clojar, and then be able to pull it down programmatically and use it from a separate project. This seemingly simple task is proving to be more complicated than I expected.

What would be ideal is if there were a way to access these resources directly, or expose them as public vars, or something. This is the first thing I tried -- I did something like this:

(ns midi.soundfont.fluid-r3
  (:require [clojure.java.io :as io]))

(def sf2
  (io/file (io/resource "fluid-r3.sf2")))

However, the problem that I'm running into is that io/resource only finds resource files on the current class path. As soon as I try to require this namespace from another project (or from the REPL), I get:

java.lang.IllegalArgumentException: Not a file: jar:file:/Users/dave/.m2/repository/midi/soundfont/fluid-r3/midi.soundfont.fluid-r3/0.1.0/midi.soundfont.fluid-r3-0.1.0.jar!/fluid-r3.sf2

If it's not possible to access the resource directly, I would be happy with a solution that involves copying the file to some path in the filesystem. I did try this as well, but I ran into the same problem when trying to run the "copy the file to the filesystem" method from a different project -- io/resource still failed to locate the file because it's not on the current classpath.

I have found similar questions that have been asked on SO previously, such as:

  • Idiomatic Clojure to copy resources from running jar to outside
  • How to copy file inside jar to outside the jar?

However these solutions only seem to pertain to copying a file that is a resource in the current (running) project.

Is it possible to do one of these two things?

  1. Access a resource file from an external clojar
  2. Import the resource file into the current project, so that I can access it using io/resource

回答1:


As dbasch correctly explained, io/resource returns a URL, not a file. But why you are being able to open that URL with io/file on the REPL or lein run but not from the jar? That's because the URL in the first case points to the plain file in the filesystem, while the URL when running with the jar points to the resource inside the jar, so it's not a proper file.

I made an example in this github repo. I'll copy the -main code here for reference:

(defn -main [& args]
  (let [r (io/resource "greet")]
    (println r)
    (println (slurp r))
    (with-open [rdr (io/reader r)]
      (println (clojure.string/join ", " (line-seq rdr))))
    (println (io/file r))))

Running with lein run shows:

› lein run
#<URL file:/home/nicolas/projects/clojure/resources/resources/greet>
hello
world

hello, world
#<File /home/nicolas/projects/clojure/resources/resources/greet>

Running the uberjar shows:

› java -jar target/resources-0.1.0-SNAPSHOT-standalone.jar 
#<URL jar:file:/home/nicolas/projects/clojure/resources/target/resources-0.1.0-SNAPSHOT-standalone.jar!/greet>
hello
world

hello, world
Exception in thread "main" java.lang.IllegalArgumentException: Not a file: jar:file:/home/nicolas/projects/clojure/resources/target/resources-0.1.0-SNAPSHOT-standalone.jar!/greet
        at clojure.java.io$fn__8588.invoke(io.clj:63)
        at clojure.java.io$fn__8572$G__8556__8577.invoke(io.clj:35)

See the difference between #<URL file:/home/nico... and #<URL jar:file:/home/nico..., that explains why you can't call (io/file) on it, but you can read it with slurp or create a reader with io/reader.




回答2:


(io/resource "fluid-r3.sf2") is a url, not a file. You can slurp it if you want it in memory all at once, or read it as a stream with the java.net.URL api (and write it to a file as you read it if you want to).

Example:

user> (type (clojure.java.io/resource "text.txt"))
java.net.URL
user> (slurp (clojure.java.io/resource "text.txt"))
"this is a text file\n"



回答3:


"Access a resource file from an external clojar"

I found this solution from this article. Dmitri Sotnikov did this at his cryogen project.

source code is here. The load-plugins functions will retrieve all the plugin.edn from under every resources directory of external clojars.

(ns cryogen-core.plugins
  (:require [clojure.edn :as edn]
            [clojure.string :as string]
            [text-decoration.core :refer :all]))

(defn load-plugin [^java.net.URL url]
  (let [{:keys [description init]} (edn/read-string (slurp url))]
    (println (green (str "loading module: " description)))
    (-> init str (string/split #"/") first symbol require)
    ((resolve init))))

(defn load-plugins []
  (let [plugins (.getResources (ClassLoader/getSystemClassLoader) "plugin.edn")]
    (doseq [plugin (enumeration-seq plugins)]
      (load-plugin (. ^java.net.URL plugin openStream)))))

If you do not like too much java interoperation, there is a resources function in pomegranate library which can retrieve all the resources with given name in external clojars.



来源:https://stackoverflow.com/questions/30857749/importing-using-a-resource-from-an-external-clojar

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!