Search code examples
javafilejarclojure

Clojure list subfolders in resources in uberjar


I have a folder structure like this:

  • resources
    • a
      • b
        • x.txt
      • c
        • x.txt

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?


Solution

  • 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) #".*")))))