beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
Louis Kottmann 2020-11-01T17:30:27.491500Z

I am trying to list all files in a configs resources directory, read them as yaml, and merge it all in a config map during dev with CIDER, this works fine:

(def config (->> (io/resource "configs")
                 (io/file)
                 (.listFiles)
                 (map yaml/from-file)
                 (reduce merge)))
However at runtime, from the jar, it fails due to:
Caused by: java.lang.IllegalArgumentException: Not a file: jar:file:/path/to/app.jar!/configs
Indeed, it is not a file, it should be a directory. I checked and the files are present in the classpath (at the root, in the configs directory as I would expect) My google-fu turned out lots of complicated answers, but I can't believe this is not more straighforward, how should I achieve this?

2020-11-02T16:14:59.025600Z

also, you can't use io/file for things inside jars - io/resource is relative to all classpath entries (which can include 0 or more actual directories on disk)

👍 1
Louis Kottmann 2020-11-01T17:51:18.492800Z

ok I solved it using:

(def project-path (.getParent
                   (io/file (.getCanonicalPath
                             (io/file "project.clj")))))

(def config (->> (str u/project-path "/resources/configs")
                 (io/file)
                 (.listFiles)
                 (map yaml/from-file)
                 (reduce merge)))
but I'm not sure if it works because the files are present next to the jar files in the docker container, or if it actually fetches the files from the jar

teodorlu 2020-11-02T10:23:18.006300Z

Yeah, I can imagine that might give you problems. Java resources don't really have directories, in the same sense that normal file systems do. The classpath specifies how different directory trees should be merged, so what you're loading from is the "merged classpath from your project, and all of your dependencies". Looking at a stackoverflow answer to a Java question[1], I suspect you'll want to do something along the lines of

Thread.currentThread().getContextClassLoader().getResourceAsStream(yourPath)
, but I suspect other Clojurians might have encountered this problem before! [1]: https://stackoverflow.com/questions/3923129/get-a-list-of-resources-from-classpath-directory

teodorlu 2020-11-02T10:24:30.006800Z

Another reference: https://gist.github.com/kkarad/748274

Louis Kottmann 2020-11-02T10:27:27.007Z

yeah I've seen the jarFile.entries() before but that outputs a shitton of files (every file from every dependency + the resources of the project), and I'm looking for just one "folder"

teodorlu 2020-11-02T14:43:27.015400Z

I guess that explains why projects "namespace their resource folders"; putting config files in resources/com/teodorheggelund/myproject rather than the top level. I don't see an easy way out for you, though, unless you can choose where to store the files.

Louis Kottmann 2020-11-02T15:22:21.016100Z

I just copy the original files and load them as normal files, bypassing the resources shenanigans

Louis Kottmann 2020-11-02T15:23:08.016300Z

and now consider resources to be a java idea gone haywire until further notice ^^

Louis Kottmann 2020-11-02T15:23:11.016500Z

thanks

practicalli-john 2020-11-01T17:57:37.493700Z

My guess is because io/resources and io/file look in different places. io/file is from the root of the project or an absolute path. io/resources is relative to the project/resources directory.

bpenguin 2020-11-01T19:16:08.494800Z

I've got a question on something I saw recently in one of the explanations of https://findka.com/blog/essays-implementation/

(defn send** [api-key opts]
  (http/post (str "<https://api.mailgun.net/v3/mail.findka.com/messages>")
    {:basic-auth ["api" api-key]
     :form-params opts}))

(defn send* [{:keys [mailgun/api-key template data] :as opts}]
  (if (some? template)
    (let [template-fn (get templates template)
          mailgun-opts (template-fn data)]
      (send** api-key mailgun-opts))
    (send** api-key (select-keys opts [:to :subject :text :html]))))

(defn send [{:keys [params template recaptcha/secret-key] :as sys}]
  (if (= template :biff.auth/signup)
    (let [{:keys [success score]}
          (:body
            (http/post "<https://www.google.com/recaptcha/api/siteverify>"
              {:form-params {:secret secret-key
                             :response (:g-recaptcha-response params)}
               :as :json}))]
      (when (and success (&lt;= 0.5 score))
        (send* sys)))
    (send* sys)))

bpenguin 2020-11-01T19:16:27.495300Z

Is there anything special going on with the * in those function names?

phronmophobic 2020-11-01T19:25:19.495400Z

nothing built into clojure. I've also never seen that convention before. not sure if that's just a convention by the author or if there some's tooling used by the author that relies on it.

phronmophobic 2020-11-01T19:30:59.495600Z

interesting, https://grep.app/search?current=3&amp;q=defn%5B%20%5D%5Ba-z-%5D%2B%5B%2A%5D&amp;regexp=true&amp;filter[lang][0]=Clojure seems to be used for naming helper functions

seancorfield 2020-11-01T19:36:10.495900Z

Feels like it would be a good addition to this section of the FAQ https://clojure.org/guides/faq#qmark_bang

seancorfield 2020-11-01T19:36:47.496100Z

The convention is typically that foo* is usually some sort of implementation piece of foo itself.

seancorfield 2020-11-01T19:37:43.496300Z

For example, you might have a function fetch-data that caches results for a short time, and it might call fetch-data* which would fetch the data but not cache it.

seancorfield 2020-11-01T19:37:54.496500Z

Does that help @btenggren20?

bpenguin 2020-11-01T19:44:03.496700Z

Yes! Thanks a bunch. I read it as breaking out peices of send which are sent explicitly but the rubyist in me saw them as splat operators and I was curious to know if there was any magic in them

seancorfield 2020-11-01T19:52:23.496900Z

You'll also see *foo* which is a convention for dynamic Vars that can be used with binding.

teodorlu 2020-11-01T20:11:37.497100Z

I believe what you've written will fetch files "next to the jar". To load from the jar, try io/resource

2020-11-01T20:12:53.497300Z

And a little bit more than merely a convention, since the Clojure compiler warns if you name a Var with "earmuffs", but do not also declare it as ^:dynamic

seancorfield 2020-11-01T20:30:00.497600Z

Does it? I thought it used to, but you don't get a warning here:

user=&gt; (def ^:dynamic foo 42)
#'user/foo
user=&gt; (binding [foo 13] (println foo))
13
nil
user=&gt; foo
42
user=&gt;

Michael W 2020-11-01T20:35:19.497800Z

user=&gt; (def *foo* 42)
Warning: *foo* not declared dynamic and thus is not dynamically rebindable, but its name suggests otherwise. Please either indicate ^:dynamic *foo* or change the name.

seancorfield 2020-11-01T20:37:38.498Z

Ah, yes, it's the other way around from what I was thinking.

seancorfield 2020-11-01T20:38:12.498200Z

So you can declare anything ^:dynamic but if you use earmuffs, you should use ^:dynamic.

Louis Kottmann 2020-11-01T23:26:46.498400Z

I did, but io/resource does not work with directories when running with an uberjar, I mean I did not find out how to use it properly