Fork me on GitHub
#beginners
<
2021-04-23
>
piyer00:04:07

Is there a way to run the lein task after the uberjar? e.g java -jar foo-snapshot.jar -task foobar ?

piyer00:04:29

I have db migration which I need to run before I deploy (ragtime migration). My build process builds the jar, I am trying to figure out how to run db migration from uberjar.

noisesmith13:04:05

I typically do this by defining a my.project.migrations namespace, and then java -cp my.jar clojure.main -m my.project.migrations arg arg1 arg2

noisesmith13:04:03

clojure is flexible enough to use its main to run any ns that has a -main, so you can have as many entry points as you like in one jar (but this is all totally outside the world of lein, which is a build / dep tool not an app runner)

noisesmith13:04:28

maybe - best treated as a build/dep tool rather than an app runner

Alex Miller (Clojure team)00:04:52

I don't think you can pass main args if you use -jar

Alex Miller (Clojure team)00:04:24

you can java -cp foo-snapshot.jar the.main -task foobar though

Alex Miller (Clojure team)00:04:49

assuming the.main is your ns with a -main and it takes -task foobar

Zaymon01:04:47

Are there any good resources for clojure testing patterns and best practices?

Zaymon05:04:30

@U01KQ9EGU79 @U06BE1L6T Thanks I’ll check out both of these

👍 3
West03:04:58

How does one read edn from a file in clojurescript? I usually just slurp.

seancorfield05:04:27

cljs running in the browser? Won’t you need to slurp the file on the server and send it to the client?

practicalli-johnny06:04:06

As an EDN file is Clojure data, the data could instead be put into a namespace in the ClojureScript project as a def and required in the relevant namespace. This assumes you don't need a specific edn file, just the data of course.

West06:04:54

So here’s what I’m doing. I’m generating a SPA using reagent. I want to be able to have all my data (stuff that I want to swap out later, like names, emails, links, localization) in a separate file. I would just call a cljs file with my data, but I also a clj file to read that data as well. I tried using cljc instead, but unfortunately it doesn’t work.

West06:04:34

In the end, it’s a static site that I want to create, and I don’t want react in there. I’d much rather have it use hiccup to spit some static html. The only reason I’m using this approach at the moment is because of a couple npm libraries and shadow-cljs. Both shadow and tailwind make it so easy to do iterative design.

practicalli-johnny06:04:10

For static sites I have just used figwheel-main +reagent and included http://bluma.io CSS library via the html page from a CDN. No npm overhead. I haven't dived into shadow, sorry. Perhaps more help on the #shadow-cljs channel

dvingo15:04:23

Using shadow-cljs:

(def fe-config (edn/read-string (shadow.resource/inline "/config/fe-config.edn")))
where the /config/... is on your classpath

dvingo15:04:30

It's essentially just the usual - wrapper around slurp in a macro, but with the (amazing) benefit of having changes to that edn file trigger hot reloads

🙌 3
West23:04:33

@U051V5LLP Dude, this is fucking awesome. Imma try it out now!

West01:04:01

I’m starting to change my mind. I might end up not using react after all. I started migrating my site to use this generator called https://github.com/OrgPad-com/volcano, and I’m starting to think that the react magic may be useful.

seancorfield06:04:06

Documenting datafy and nav seems very problematic because the semantics are both very poorly documented in Clojure itself and very poorly understood even by people who implement these protocols.

Basile11:04:56

Hi everyone, Would someone be able to point me to beginner resources about building a browser extension in clj or cljs? I cannot find a great deal of things out there and struggling with making my own tooling from existing tools as I’m at the very, very beginning of my learning 🙂

NikolaMandic11:04:12

I did not do this but my guesses are that if you did not yet check how to build extension without cljs first check that like google how to make browser extension then look how to compile cljs file to javascript then check how to reference variables that webextension would reference from cljs like globals in browser

NikolaMandic12:04:17

so if your extension would in javascript do something like window.navigator bla bla then you can reference same global from clojurescript you just have to compile clojurescript and make sure when you package extension you put everything clojurescript needs in there

prnc12:04:28

There is chromex:

prnc13:04:04

From my limited experience anything but a trivial extensions, can actually be quite tricky (so if you’re really “at the very, very beginning of my learning” be prepared for that 😉 )

Basile13:04:42

Thank you @U7MHWDLD8 I’m going to look at Chromex. Cheers @U01UWMWMD5L – seems like this is more “the clojure way”: understand it properly, then build it youself 🙂

Robert Möstl13:04:31

Hello! I can't seem to find where the loading of user.clj is documented. Can someone please point me in the right direction? Background: Wanted to start my ring-based webserver automatically when starting nREPL in Cursive and discovered the existence of user.clj in https://cognitect.com/blog/2013/06/04/clojure-workflow-reloaded by coincidence more or less.

noisesmith13:04:26

user.clj is loaded if found on the classpath, when you load a file all the code in it gets run

noisesmith13:04:02

that's really all there is to know (besides of course idioms / cleverness for setting up your dev env by putting fun and useful things in user.clj)

noisesmith13:04:48

I personally have a ~/user.clj that isn't on class path, and use load-file to source it into a repl, because having to explicitly source it eliminates a source of weirdness from my dev flow

noisesmith13:04:32

just like any other clojure file, any top level side effects run when the file loads, and any definitions get installed in that file's ns (user, for user.clj, of course)

noisesmith13:04:56

I think of it as clojure's dot file for local dev (and similarly I keep my dot files as empty as possible except when it comes to UI flow tweaks)

noisesmith13:04:06

also - don't put a user.clj file in your repo - instead, use config to add its location to that repo's classpath, a user.clj shouldn't end up in your jar / deploy

Robert Möstl13:04:09

I understood that already, but was looking for more in-depth information to understand the platform as a whole.

noisesmith13:04:20

there's nothing else to know frankly

noisesmith13:04:48

it can do what any other clojure file can do when loaded

Robert Möstl13:04:52

Yeah, but I kind of wonder why it's not mentioned anywhere on official http://clojure.org.

noisesmith13:04:32

probably because it's so simple / supported but not encouraged? but that's mind reading, a clojure core dev should answer that

Robert Möstl13:04:47

Anyway, I appreciate you answers nonetheless.

noisesmith13:04:06

one design concern is the conflict between a personal project-agnostic user.clj and a user.clj that provides behavior to one project, arguably the latter is better off being a custom entry point (nothing stops you from having a -main for dev that both launches a web server and an nrepl server)

Robert Möstl13:04:55

Is it a REPL's job to load user.clj? Or is it baked into clojure core?

ghadi13:04:14

clojure core

noisesmith13:04:10

that has to be my easiest clojure search source yet the only mention of the string in the codebase

ghadi13:04:11

some of us are not a fan of user.clj -- the presence of it can lead to a surprising initialization process

💯 3
Robert Möstl13:04:10

There we go, thanks!

noisesmith13:04:35

yeah I consider checking a user.clj into a repo bad hygiene, similar to putting a .emacs or .vim file in a project

Robert Möstl13:04:52

Next logical question would be: How else do I bring nREPL to execute some app init code on startup?

noisesmith13:04:15

add nrepl as a dev profile lib, have a main that starts it

noisesmith13:04:45

nearly a one liner

Robert Möstl14:04:16

"it" being the server? In my case the ring.adapter.jetty.run-jetty call

noisesmith14:04:17

it being nrepl, the jetty call is a second one liner

noisesmith14:04:36

that doc also has build tool config for implicitly starting nrepl alongside another task

Robert Möstl15:04:36

Ah, thanks. Guess I should have explained my setup better, though. My current noob setup is to start the REPL and within the REPL start the jetty-adapter with my root request handler. If I understand correctly, you suggest to have an ordinary main and start the REPL in there. The reverse somewhat.

Robert Möstl15:04:31

Btw, I am aware that I should probably not operate my webserver via a REPL in production.

noisesmith14:04:30

for safety you might want the ns that starts nrepl to not end up in your build

bnstvn14:04:52

with tools.cli, can i parse an option with an optional value? so somehow --opt 123 --other and --opt --other should parse to {:opt 123 :other true} and {:opt true :other true} (not to {:opt "--other"} )

seancorfield15:04:31

@ban.istvan No. An option either has a value (argument) or it is a boolean toggle. It can’t be both.

🙏 3
piyer15:04:22

@seancorfield I liked the ddl on the old next.jdbc, I couldn't find ddl docs on the seancorfield/next.jdbc, any pointer? I can also use the old one just for the migration, wondering what is the recommended way.

seancorfield15:04:48

@munichlinux What DDL in c.j.j do you mean?

seancorfield15:04:38

You’re just going to call jdbc/execute-one! with a SQL string to do DDL stuff, right?

seancorfield15:04:21

c.j.j only provided half-hearted CREATE TABLE and DROP TABLE helper functions — you still had to execute that SQL. If you want more of a DSL for building SQL, look at HoneySQL: it has extensive DDL support in v2. See https://cljdoc.org/d/com.github.seancorfield/honeysql/2.0.0-beta2/doc/getting-started/sql-clause-reference#ddl-clauses and https://cljdoc.org/d/com.github.seancorfield/honeysql/2.0.0-beta2/api/honey.sql.helpers for details.

seancorfield16:04:20

@munichlinux Feel free to ask any related Qs in #honeysql and/or #sql (I’ll be more likely to see Qs there than here).

piyer16:04:34

nice, Thank you

seancorfield16:04:55

And don’t be put off by HoneySQL v2’s Beta status — quite a few folks are already using it in production. We have a mix of v1 and v2 in production ourselves.

piyer16:04:38

perfect! I am taking all that to production as well.

seancorfield16:04:12

FWIW, at work we wrote our own ad hoc migration setup, but if I was starting over with that I would use migratus

seancorfield16:04:54

(and we just use standalone SQL files, with a Clojure script that sucks them in and runs jdbc/execute-one! on each statement)

piyer16:04:48

Looking at migratus. I was thinking about using ragtime.

Endre Bakken Stovner16:04:48

I have a function like:

(defn a-fn []
  (let [v1 (compute-v1)
        v2 (compute-v2 v1)
        v3 (compute-v3 v2)
        v4 (compute-v4 v3)]
    v4))
Every computation compute{v1..v4} might fail (indeed - they are expected to often do so). In that case I'd like to show a nice error message (perhaps with more metadata and suggestions for what might have gone wrong) to the user. Is there a nice pattern for doing this? I could have a lot of try/catches but that feels non-functional.

hiredman16:04:53

instead of returning just the value return a map of {:error ...} or {:value ...} and write all your compute functions to pass maps that contain :error through unchanged

hiredman16:04:43

(the Either monad if you need a name for it)

Endre Bakken Stovner16:04:45

Feels very functional (monadic-ish). Thanks!

Endre Bakken Stovner16:04:53

I should have thought of it.

hiredman16:04:01

if you can then add things like (defn app [f] (fn [x] (if (contains? x :error) x (f (:value x)))))

Endre Bakken Stovner16:04:05

Yes, yes clever! I have played around with Haskell, so I like this solution a lot.

hiredman16:04:56

or I guess (defn app [f x] (if (contains? x :error) x (f (:value x)))) would be better for the name app

Endre Bakken Stovner16:04:55

Sometimes the error messages occur in libraries I use. I rely on stuartsierra/dependency which throws an error in case of a circular DAG for example:

(-> (dep/graph)
    (dep/depend :a :a))

;; Execution error (ExceptionInfo) at com.stuartsierra.dependency.MapDependencyGraph/depend (dependency.cljc:89).
;; Circular dependency between :a and :a
What is the best way to collect such errors?

Endre Bakken Stovner16:04:04

Just have a general catch Exception and put the Exceptions collected into the map I pass around? Also, this feels like it would be a lot of duplication of code. In Python it seems like the kind of thing I'd do with decorators. Should really read up on this.

dpsutton16:04:27

(defn app [f x] (if (contains? x :error) x (try (f (:value x)) (catch Exception e (assoc x :error (ex-data e))))))

🙏 3
dpsutton16:04:42

ex-data probably isn't what you want but it's probably a part of what you want to get from any errors

ghadi16:04:50

@endrebak85 most libraries do not throw exceptions, except to mean 1) "you're holding it wrong" or 2) this is unrecoverable

ghadi16:04:45

e.g. network disconnect -> that's an exception

ghadi16:04:19

carefully choose places to catch and turn exceptions into data

Endre Bakken Stovner16:04:34

I see and agree. I never throw exceptions, but a few of the libraries I use might. Also I like the map of :error and :value approach because then I can add a :warnings-key too.

ghadi16:04:41

cognitect.anomalies is another approach in this space:

sova-soars-the-sora17:04:46

It's interesting reading the ongoing discussion about error handling. I know Java pretty much demands the use of lots of try/catch blocks, but I don't use them at all in Clojure ...

ghadi17:04:23

you'll need to use them any time you interact with IO

Endre Bakken Stovner17:04:48

I'm actually tempted to just have a top level try/catch for now. I'll use a more advanced pattern when I see a need for it.

ghadi17:04:51

Clojure isn't isolated from having to handle problems coming from disk or network or buggy libs

sova-soars-the-sora17:04:29

that's a fair point. so mainly for IO

Endre Bakken Stovner17:04:15

I am getting this error when trying to pprint a vec in my code:

java.lang.ClassCastException: class clojure.lang.PersistentVector cannot be cast to class java.io.Writer (clojure.lang.PersistentVector is in unnamed module of loader 'app'; java.io.Writer is in module java.base of loader 'bootstrap')
When I try in a REPL it works just fine. What might be wrong?

hiredman17:04:06

somewhere something has the arguments reversed

🙏 3
Endre Bakken Stovner17:04:09

The problem was that I was sending two arguments to pprint. (print "a" "b") works, but not pprint.

bastilla19:04:53

A very basic question: (In Clojurescript) I generate some &lt;input&gt; tags (hiccup), and the first one should be set with {:autofocus true}, the rest without. So how to single out the first element as such. (Very basic if you control the RAM, but in FP not so much.) PS: I loop via (for [x map] ...). Thanks!!

Aron19:04:15

there are many ways, but probably you want something more idiomatic for generating UI than a for loop

bastilla19:04:46

I'd take the "most" idiomatic approach there is. Especially if that info (first element) is then easily accessable. So far I did't see any probs with for.

practicalli-johnny10:04:29

I've used for quite a lot for streamlining hiccup style code in ClojureScript. So I am curious at so what is more idiomatic than for in Clojure in this case. This has been mentioned to me before, but haven't been offered alternatives. The constraint I am aware of is that for is not composable, but that is not an issue for the hiccup generation code I'm writing.

Aron10:04:50

could be totally fine in that particular case 🙂 I hope my casual comment doesn't lead to people questioning their working practice, that would be the opposite of what I wish for

Aron10:04:59

if you share some example, that would be easier to discuss specifically without making bad general judgments :)

practicalli-johnny17:04:34

Just curious as someone said something similar a few years ago but didnt elaborate. I like for as its list comprehension is very simple and very powerful for specific cases.

seancorfield19:04:11

@bastilla perhaps (for [[x auto] (map vector your-data (cons true (repeat false)))] ...) so auto will be true for the first item and false for all the rest.

phronmophobic19:04:35

I would probably write something like:

(for [[i x] (map-indexed vector xs)
      :let [first? (zero? i)]]
  [:div (merge {:normal-prop "foo"}
               (when first?
                 {:autofocus true}))
   ...])

seancorfield19:04:49

(but there are all sorts of possible answers)

3
az19:04:34

Any ideas why running lein run on a luminus project wouldn’t generate an .nrepl-port file even though nrepl is starting up?

bastilla19:04:13

I get your approach @seancorfield. And @smith.adriane, I don't know map-indexed yet, but will definitely have a look at it. Meanwhile I came up with this idea:

(doall (for [x map :let [first? (= x (first map))]] ...))
If this is horrendous, pls grab my arm. I'd also like to say (again) how awesome it is to have folks like @seancorfield and all these illuminaries answer to noobs like me. Again, this community is really supportive. ok, 'nuff lickspittling, just saying.

phronmophobic19:04:59

I would advise against checking (= x (first map)) since it may return true if map contains duplicates.

seancorfield19:04:35

Instead of (doall (for [x map] (do-stuff :to x))) you could do (mapv #(do-stuff :to %) map) and then you’re guaranteed a vector result and you could just (assoc-in result [0 :autofocus] true) — or whatever is the appropriate path into that first element.

phronmophobic19:04:58

I guess it won't have duplicates in the case that map is a hashmap, but if you reuse the same code elsewhere, it might get you into trouble

seancorfield19:04:32

Given it’s using for, I wouldn’t expect it to be a hash map but maybe…?

phronmophobic19:04:04

right, but it is called map 🤷

Aron19:04:42

(let [arr ["a", "b", "c"]] (vec (concat [(clojure.string/upper-case (first arr))] (rest arr))))

bastilla20:04:16

Yes, it's a map. Thanks a lot (and @seancorfield @smith.adriane @U0VQ4N5EE`)`. I have plenty of mind food again.

seancorfield20:04:57

@bastilla You know that hash maps are unordered, so you’ll get your fields in a random order, depending on how many items are in the map and what their keys are?

seancorfield20:04:56

So there’s really no sense of “first” in a hash map — it’s just a random element that happens to appear in the first slot when the hash map is converted to a sequence…

bastilla20:04:23

Yes, I know. Idea was: first should get the same elem as for (at first take).

bastilla20:04:14

But I guess this isn't guarantueed.

seancorfield20:04:16

True, it does — because seq is repeatable for the same hash map. But getting your HTML fields in a random order seems… weird…

bastilla20:04:46

Concerning HTML forms, you're right. I was just curious and had this question at other occasions, too.

Oliver19:04:25

How can I filter on a nested data structure, eg. I want to filter out all even numbers in [{:a [0 1 2 3 4]} {:b [5 7 8 9]}] => [{:a [1 3]} {:b [5 7 9]}] ?

tavistock19:04:05

the easiest way to do this is via (mapv #(into {} (map (fn [[k v]] [k (filterv odd? v)]) %)) xs) , if you need to do this for arbitrarily nested lists then you might need to use something from clojure.walk

3
Lu20:04:15

I’d rather do this purely for readability purposes

(for [entry data
      [k v] entry]
  {k (filter odd? v)})

clojure-spin 3
Darrell22:04:07

Is there an idiomatic way of assigning a conditional value? For example; if foo is empty then assign x to “bar” else assign x to “baz”

hiredman22:04:40

(let [x (if foo "bar" "baz")] ...)

Darrell22:04:55

Hm, I could swear I tried that.

Darrell22:04:09

Ah, I see what I did wrong. Thanks @hiredman!