siltalau
siltalau

Reputation: 549

Clojure list subfolders in resources in uberjar

I have a folder structure like this:

I've created my runnable jar with lein uberjar.

When running my jar, I want to list the subfolders of a.

I know I can get the contents of resources/a/b/x.txt using clojure.java.io

(slurp (clojure.java.io/resource "a/b/x.txt"))

But I can't find a simple way to list the subfolders.

(clojure.java.io/file (clojure.java.io/resource "a")) just results in a java.lang.IllegalArgumentException: Not a file because it isn't a file, it's a resource inside the jar file.

Is there a library that does this?

Upvotes: 4

Views: 1665

Answers (3)

Herman Nurlygayanov
Herman Nurlygayanov

Reputation: 104

You can recursively list all items in a dir using file-seq function. Example:

(let [files (file-seq (clojure.java.io/file "resources"))
      dir? #(.isDirectory %)]
  (map dir? files))

You can also call .listFiles or .list on a File object. The first gives file objects, the second gives filenames.

You can find additional examples here and here.

Upvotes: 0

Svante
Svante

Reputation: 51531

A different approach (but I must admit that it doesn't feel entirely satisfactory) is to read the contents at compile time. Assuming you have a function list-files that gives you the list from your project root directory:

(defmacro compile-time-filelist []
  `'~(list-files "resources/a"))

or

(defmacro compile-time-filelist []
  (vec (list-files "resources/a")))

Upvotes: 1

leetwinski
leetwinski

Reputation: 17859

here is the port of code from the java specific answer:

(ns my-project.core
  (:require [clojure.string :as cs])
  (:import java.util.zip.ZipInputStream)
  (:gen-class))

(defrecord HELPER [])

(defn get-code-location []
  (when-let [src (.getCodeSource (.getProtectionDomain HELPER))]
    (.getLocation src)))

(defn list-zip-contents [zip-location]
  (with-open [zip-stream (ZipInputStream. (.openStream zip-location))]
    (loop [dirs []]
      (if-let [entry (.getNextEntry zip-stream)]
        (recur (conj dirs (.getName entry)))
        dirs))))

(defn -main [& args]
  (println (some->> (get-code-location)
                    list-zip-contents
                    (filter #(cs/starts-with? % "a/")))))

Being put to a main namespace and run with jar will output all the paths in the /resources/a folder..

java -jar ./target/my-project-0.1.0-SNAPSHOT-standalone.jar 
;;=> (a/ a/b/ a/b/222.txt a/222.txt)

Also some quick research lead me to this library: https://github.com/ronmamo/reflections

it shortens the code, but also requires some dependencies for the project (i guess it could be undesirable):

[org.reflections/reflections "0.9.11"]
[javax.servlet/servlet-api "2.5"]
[com.google.guava/guava "23.0"]

and the code is something like this:

(ns my-project.core
  (:require [clojure.string :as cs])
  (:import java.util.zip.ZipInputStream
           [org.reflections
            Reflections
            scanners.ResourcesScanner
            scanners.Scanner
            util.ClasspathHelper
            util.ConfigurationBuilder])
  (:gen-class))

(defn -main [& args]
  (let [conf (doto (ConfigurationBuilder.)
               (.setScanners (into-array Scanner [(ResourcesScanner.)]))
               (.setUrls (ClasspathHelper/forClassLoader (make-array ClassLoader 0))))]
    (println
     (filter #(cs/starts-with? % "a/")
             (.getResources (Reflections. conf) #".*")))))

Upvotes: 2

Related Questions