Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to load program resources in Clojure

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.

like image 573
clartaq Avatar asked Jan 11 '10 19:01

clartaq


3 Answers

(clojure.java.io/resource "myscript.js")
like image 58
Joe Snikeris Avatar answered Nov 08 '22 04:11

Joe Snikeris


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.

like image 25
clartaq Avatar answered Nov 08 '22 04:11

clartaq


you can also use clojure.lang.RT/baseLoader

(defn serve-public-resource [path]
  (.getResourceAsStream (clojure.lang.RT/baseLoader) (str "public/" path))) 
like image 4
miaubiz Avatar answered Nov 08 '22 05:11

miaubiz