4

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?

siltalau
  • 529
  • 3
  • 17
  • Possible duplicate of [How do I list the files inside a JAR file?](https://stackoverflow.com/questions/1429172/how-do-i-list-the-files-inside-a-jar-file) – leetwinski Sep 29 '17 at 12:11
  • Neither of those show either 1) a simple way to do this 2) a library that does this. I refuse to accept that the answer to this is a massive bunch of java code. – siltalau Sep 29 '17 at 12:22
  • well.. it is kind of a trivial task, to translate java code to clojure. – leetwinski Sep 29 '17 at 13:49
  • This can be done with a one-liner in shell (`zipinfo -1 ...`). Do you need to do it from/with clojure code? – Micah Elliott Sep 29 '17 at 16:22

3 Answers3

2

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) #".*")))))
leetwinski
  • 17,408
  • 2
  • 18
  • 42
  • I've accepted this as the correct answer - which it technically is. The complexity of the solution tells me I'm trying to do the wrong thing entirely. – siltalau Sep 30 '17 at 03:52
1

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")))
Svante
  • 50,694
  • 11
  • 78
  • 122
0

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.

  • The challenge here is that when running from an uberjar, there's no longer a directory—just contents of a zip file. – camdez Feb 07 '23 at 15:06