Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Opening file in the same directory as running code in Clojure

Tags:

clojure

I'm trying to write some tests with Clojure. One of these tests involves opening an html file in the same directory as the running test file, to use the contents as test input (a widely-used idiom in Python). I thought the *file* var would do the job, but that's not really the case, since it's relative.

Let's say my project, which uses the default leiningen layout, is called demoproj. My tests are then in ~/projects/demoproj/test. This directory replicates the project namespace again, which I'm not a big fan of, but whatever. So the tests for core.clj are in ~/projects/demoproj/test/demoproj/test/core.clj. I also have a file called small_page.html in the same directory. If I put the following into test/core.clj:

(println (-> (java.io.File. *file*)
             .getPath))

Here's what I get:

demoproj/test/readibility.clj

This is relative to the test directory of the project base. I tried reading the sample html page using only this relative path, as follows:

(slurp (-> (java.io.File. *file*)
            .getParent
            (java.io.File. "small_page.html")
            .getPath))

This causes an IO error because the file cannot be read at that location. My next idea was to get the current working directory and join it with the relative path. Here is how I fetched the working directory:

(println (-> (java.io.File. ".")
             .getCanonicalPath))

Which returned an absolute path:

/Path-to-home/projects/demoproj

The bad thing is, these two don't join to the correct path to the file; there's a test component missing in between. Joining these two paths thus leads to an incorrect directory path.

So, my question would be, is there a reliable way to get the absolute path of the code file currently executed? And if this is not possible, what would be an alternative to this idiom, i.e. how could I parse a file that is in a location relative to the test file?

like image 867
Ulas Turkmen Avatar asked Apr 08 '13 09:04

Ulas Turkmen


2 Answers

Because clojure code and associated resources can end up in a jar, and you want the code to run even if the file you are accessing can end up as part of the jar rather than a real file system, the reliable way to do this is to use io/resource, and ensure that the file in question is in a resource-path.

For example, I have the following as a part of my directory structure in a current project:

|-- project.clj
|-- resources
|   |-- caribou.properties
|   |-- config
|   |   |-- boot.clj
|   |   |-- development.clj
|   |   |-- local-dumped.clj
|   |   |-- local.clj
|   |   |-- production.clj
|   |   |-- staging.clj
|   |   `-- test.clj
|   `-- logging.properties

I have the following in my project.clj:

  :resource-paths ["shared" "resources"]

then I can do the following in my repl:

user> (do (require '[clojure.java.io :as io]) (io/resource "config/boot.clj"))
#<URL file:/Users/me/this-project/resources/config/boot.clj>
user> (slurp *1)
"(use '[caribou.config :only (read-config configure environment)])\n(require '[clojure.java.io :as io])\n..."

as you can see, io/resource returns a URL that I can then pass to slurp up the file I ask for (looking up the path fragment in the configured resource paths). This code still works if the whole app is wrapped up into an uberjar for deployment, whereas making an io/file and calling getParent fails in this case.

like image 182
noisesmith Avatar answered Nov 09 '22 05:11

noisesmith


I figured out how to do this after googling a little bit more, and actually reading all the answers on Stackoverflow. user83510 actually answered the question here: https://stackoverflow.com/a/11498784/1285782 , but the answer did not get accepted or properly upvoted. The trick is the following:

(-> (ClassLoader/getSystemResource *file*) clojure.java.io/file .getParent)

This gives you the absolute path to the directory. Not beautiful, but works.

like image 43
Ulas Turkmen Avatar answered Nov 09 '22 07:11

Ulas Turkmen