How do you load program resources such as icons, strings, graphical elements, scripts, and so on in a Clojure program? I am using a project layout similar to that in many Java projects where there is a "resources" directory hanging off of a "source" directory. A jar file is created from the source and includes the resources, but I can't seem to get the resources loaded as I would in Java.
The first thing I tried was something like
(ClassLoader/getSystemResource "resources/myscript.js")
But could never find the resource.
You can do something similar with
...
(let [cls (.getClass net.mydomain.somenamespace)
strm (.getResourceAsStream cls name) ]
...
where name is the name
of the resource to load, but the stream is nil
.
You can try using the context class loader with something like
...
(let [thr (Thread/currentThread)
ldr (.getContextClassLoader thr)
strem (.getResourceAsStream ldr name)]
...
But strem
is always nil.
In frustration, I've tried placing the resource files in just about every directory in the program. They get copied into the jar correctly, but I still can't seem to load them.
I've looked at the language sources for the load
function and the run-time library, but am not "getting" it.
Any help would be appreciated.
EDIT: Here's a more concrete example. In Java, if you wanted to convert MarkDown to HTML, you might use the showdown.js
script and write something like:
package scriptingtest;
import java.io.InputStreamReader;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
public class Example {
private Object converter;
public String transformMarkDown(String markdownString) {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("js");
try {
engine.eval(new InputStreamReader(getClass().getResourceAsStream(
"resources/showdown.js")));
converter = engine.eval("new Showdown.converter()");
} catch (Exception e) {
return "Failed to create converter";
}
try {
return ((Invocable) engine).invokeMethod(converter, "makeHtml",
markdownString).toString();
} catch (Exception e) {
return "Conversion failed";
}
}
public static void main(String[] args) {
System.out.println(new Example().transformMarkDown("plain, *emphasis*, **strong**"));
}
}
when I create the project, it all gets compiled and packed into a jar. When run, the program outputs <p>plain, <em>emphasis</em>, <strong>strong</strong></p>
A literal translation to Clojure seems pretty straightforward, but I run into trouble trying to create the InputStreamReader
-- I can't seem to write the code needed to find the script file in the jar.
Edit: Added "markdown" tag since the post gives two complete examples of approaches to processing markdown.
(clojure.java.io/resource "myscript.js")
It's the directory structure.
Continuing with the scripting engine example in the OP, a Clojure equivalent would be:
(ns com.domain.example
(:gen-class)
(:import (java.io InputStreamReader))
(:import (javax.script ScriptEngineManager ScriptEngine)))
(defn load-resource
[name]
(let [rsc-name (str "com/domain/resources/" name)
thr (Thread/currentThread)
ldr (.getContextClassLoader thr)]
(.getResourceAsStream ldr rsc-name)))
(defn markdown-to-html
[mkdn]
(let [manager (new ScriptEngineManager)
engine (.getEngineByName manager "js")
is (InputStreamReader. (load-resource "showdown.js"))
_ (.eval engine is)
cnv-arg (str "new Showdown.converter().makeHtml(\"" mkdn "\")")]
(.eval engine cnv-arg)))
(defn -main
[]
(println (markdown-to-html "plain, *emphasis*, **strong**")))
Note that the path to the resources is com/domain/resources
for this code as opposed to com/domain/scriptingtest/resources
in the Java version. In the clojure version, the source file, example.clj
is in com/domain
. In the Java version, the source file, Example.java
is in the com/domain/scriptingtest
package.
When setting up a project in my IDE, NetBeans, the Java project wizard asks for an enclosing package for the source. The Clojure plugin, enclojure, asks for a namespace, not a package. I had never noted that difference before. Hence the "off-by-one" error in the directory structure expected.
you can also use clojure.lang.RT/baseLoader
(defn serve-public-resource [path]
(.getResourceAsStream (clojure.lang.RT/baseLoader) (str "public/" path)))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With