Fork me on GitHub
#clojure
<
2021-12-21
>
Max03:12:10

I’m trying to make a custom throwable subclass that I can catch by name. I’ve copied the https://clojuredocs.org/clojure.core/gen-class#example-60e69dfee4b0b1e3652d7513 from clojuredocs, but how do I actually use it? I tried adding (:import myproject CustomException) to my ns form, but it tells me it can’t find a class with that name. I get the sense I’m supposed to do something with my ~package.json~ project.clj to get it compiled, but I’m not sure what. Are there any decent docs or examples out there on how to do this? To head the obvious responses off at the pass: I know custom exception subclasses aren’t idiomatic, and I know there are other ways to make unnamed anonymous subclass instances. I’m specifically trying to throw an exception that I can catch without accidentally catching other exceptions and messing up their stack traces for a weird experiment thing.

seancorfield03:12:48

Maybe share the code you've tried + your project.clj in a thread?

seancorfield03:12:45

I haven't used Leiningen for years, but I'm happy to try and help with this @U01EB0V3H39

seancorfield03:12:34

FWIW, you'll probably need this in your project.clj:

:prep-tasks ["compile"]
  :aot [my.CustomException]
That tells lein to run the compile task before any task you ask for (e.g., lein repl), and :aot tells lein which namespaces to compile.

ghadi03:12:46

why do you want a custom exception class?

seancorfield03:12:38

Then you can do:

$ lein repl
Compiling my.CustomException
...
max.core=> (import '(my CustomException))
my.CustomException
max.core=> (CustomException. "Ex" (ex-info "t" {}) {:a 1})
#error {
 :cause "t"
 :data {}
 :via
 [{:type my.CustomException
   :message "Ex"
   :data {:a 1}
   :at [max.core$eval1619 invokeStatic "form-init4063394007754078810.clj" 1]}
  {:type clojure.lang.ExceptionInfo
   :message "t"
   :data {}
   :at [max.core$eval1619 invokeStatic "form-init4063394007754078810.clj" 1]}]
...

seancorfield03:12:04

@U050ECB92 he said it was "for a weird experiment thing."

1
seancorfield03:12:17

(he posted a whole bunch of caveats he wanted to head off 🙂 )

🙏 1
Max04:12:35

Thanks Sean! Here’s the file I’m trying to gen-class: scratch/CustomException.clj

(ns scratch.CustomException
  (:gen-class
   :extends java.lang.Error
   :constructors {[String Throwable clojure.lang.IPersistentMap] [String Throwable]} ; mapping of my-constructor -> superclass constuctor
   :init init
   :state state ; name for the var that holds your internal state
   :main false
   :prefix "my-ex-"))

(defn my-ex-init [msg t context]
  ;; first element of vector contains parameters for the superclass constructor
  ;; Second element will be your internal state 
  [[msg t] context])

(defn my-ex-getData [this]
  (.state this)) ; accessing the internal state
I basically just copied it from the linked clojuredocs example. My project.clj is similarly bare-bones:
(defproject scratch "0.1.0-SNAPSHOT"
  :dependencies [[org.clojure/clojure "1.10.3"]]
  :main ^:skip-aot scratch.core
  :target-path "target/%s")
I tried to import the class in scratch/core.clj:
(ns scratch.core
  (:import scratch CustomException))
and I get a ClassNotFoundException

Max04:12:56

Looking into this a little more, it seems like in the REPL I might need to compile the class? I ran (compile 'scratch.CustomException) but I still get the class not found exception

Max04:12:04

I’ll try the leiningen think you suggested as well and see if that works. it’d still be nice to have a way to iterate on it in the repl

Max04:12:12

Ok, trying the project.clj changes you suggested, I get this error (abbreviated) when starting the repl:

[{:type clojure.lang.Compiler$CompilerException
   :message Syntax error compiling at (scratch/core.clj:1:1).
   :data #:clojure.error{:phase :compile-syntax-check, :line 1, :column 1, :source scratch/core.clj}
   :at [clojure.lang.Compiler load Compiler.java 7652]}
  {:type java.lang.ClassNotFoundException
   :message scratch
   :at [java.net.URLClassLoader findClass URLClassLoader.java 471]}]

Max04:12:03

Maybe my ns’s/files aren’t matching up? What should a gen-class’d clj file be named?

seancorfield06:12:22

It should be (:import (scratch CustomException))

seancorfield06:12:33

or (:import scratch.CustomException)

seancorfield06:12:26

In the first one, you're importing CustomException from the package scratch -- and you can import multiple classes if needed. In the second one, you're importing a specific class.

seancorfield06:12:55

In your code, you're telling it to import scratch and to import CustomException, neither of which exist as top-level classes.

seancorfield06:12:00

(sorry I didn't answer sooner but it seemed you'd already gone to bed so I wasn't watching Slack after a while)

Max14:12:58

No worries, I was bouncing around between a few things last night. I really appreciate the help!

Max14:12:00

Ok, I think I’ve got everything hooked up now. Only one small thing isn’t working: when I make changes to the gen-classed ns, I can’t seem to see those changes in other ns’s without restarting the repl. I’ve been running (compile scratch.CustomException) , but I think perhaps my (:import ) isn’t refreshing even when I re-eval the ns?

seancorfield17:12:31

Yeah, I'm not sure that you can hot reload gen-class'd classes. I try to avoid gen-class as much as possible because it's a bit of a mess all around.

Max17:12:38

What’s the alternative? Can you hot-reload plain old .java files?

noisesmith19:12:30

java files are not classes, in java when you recompile a class the program generally needs to be restarted before you can use the new version

noisesmith19:12:07

java doesn't have "loading files" the way clojure does, as the compiler is not built into the vm

noisesmith19:12:42

(IDE's provide versions of this, I'm not specific on the details, I assume the cursive IDE is most likely to make this usable)

hiredman03:12:37

package.json is not a clojure thing

Max03:12:04

picard-facepalm I meant project.clj

kah0ona08:12:34

Hello Folks, question: I am using clojure.tools.logging, and logback for a good while now. But I never really got line numbers to work in the logs. Before, I used Timbre, with which that worked, but there are some reasons I want to use tools.logging. Is it possible?

hiredman08:12:00

Likely not, I believe logback inspects the stack to get line numbers, and tools.logging, if I recall, can add stackframes

borkdude08:12:05

you can use timbre through tools.logging btw

kah0ona08:12:32

So like: require timbre, configure it to use tools.logging, configure THAT to use logback?

kah0ona08:12:45

require timbre in the code I mean

borkdude08:12:14

no, you said, you wanted to use tools.logging rather than timbre, but there is a timbre adapter for tools.logging, so they are not mutually exclusive

borkdude08:12:33

e.g. babashka exposes clojure.tools.logging with timbre as the logging impl

kah0ona08:12:43

yeah ok yeah gotcha

kah0ona08:12:50

thanks! will look into that

hoynk14:12:20

Is there a way to create one spec file tha references all the others so I can only require one file and have all specs loaded?

Alex Miller (Clojure team)15:12:42

sure, just use require to load the others like any other clj file

Steve H22:12:43

Heyo, I'm new to Clojure and trying to execute a simple jQuery script using etaoin. This is what I have:

(js-execute d "$('#mainmenuli-report').trigger('mouseenter');")
Here's my error:
loaders.core=> (js-execute d "$('#mainmenuli-report').trigger('mouseenter');")
Execution error (ExceptionInfo) at slingshot.support/stack-trace (support.clj:201).
throw+: {:response {:sessionId "95536bb4edb78c51dcbf593d8f8c1325", :status 17, :value {:message "javascript error: 
$ is not defined\n  (Session info: chrome=96.0.4664.110)\n  (Driver info: chromedriver=96.0.4664.45 (76e4c1bb2ab4671b8beba3444e61c0f17584b2fc-refs/branch-heads/[email protected]{#947}),platform=Windows NT 10.0.19043 x86_64)"}}, :path "session/95536bb4edb78c51dcbf593d8f8c1325/execute", :payload {:script "$('#mainmenuli-report').trigger('mouseenter');", :args []}, :method :post, :type :etaoin/http-error, :port 35178, :host "127.0.0.1", :status 200, :driver {:args ("chromedriver" "--port=35178"), :capabilities {:loggingPrefs {:browser "ALL"}, :chromeOptions {:args ("--window-size=1400,850")}}, :process #object[java.lang.ProcessImpl 0x2ef5c28d "Process[pid=20916, exitValue=\"not exited\"]"], :locator "xpath", :type :chrome, :env nil, :port 35178, :host "127.0.0.1", :url "", :session "95536bb4edb78c51dcbf593d8f8c1325"}}
Appreciate any thoughts or workarounds.

dpsutton22:12:17

do you have jQuery on that page? > $ is not defined

Steve H22:12:24

Hello, so if I run $('#mainmenuli-report').trigger('mouseenter'); in the console the desired effect is acheived (mouseover a hidden navigational element to reveal sub-nav). Just not sure of the proper syntax to use with etaoin, if this is even possible?

delaguardo06:12:24

Are you using chrome console for that? It adds $ as a synonym of document.querySelector but it is available only in the context of console.

Steve H16:12:06

I am @U04V4KLKC, thanks for clarifying

dpsutton22:12:38

i have no other suggestions. There’s an #etaoin channel where you might have good luck finding someone well-versed in that library

🙏 2