Fork me on GitHub
#clojure
<
2020-10-06
>
vlaaad05:10:22

Expectations for tools.build keep growing 😂

Kevin08:10:01

Anyone know of a good Clojure(not script) TUI library? Bonus points for GraalVM compatibility

Kevin09:10:25

Thanks, I'll take a look at these options

borkdude09:10:33

@UG9U7TPDZ Quickly tried to compile tetris: https://github.com/borkdude/console-tetris#graalvm It seems it gets quite far, but there's an error in the final link step.

borkdude09:10:28

I'll make a graalvm issue

Kevin09:10:11

I think that Lanterna version is pretty outdated. Laterna seems to have updated to 3.1 recently

Kevin09:10:24

Maybe interop would be a better option

Kevin09:10:57

I'll take a look after work

borkdude09:10:58

yeah. would also be interesting to see if this may work for bb

🙌 3
Kevin09:10:03

If you could seamlessly build tui apps with bb, that would be amazing

borkdude09:10:57

Totally forgot about that one

borkdude10:10:19

He uses the new lanterna and made his own wrapper

Kevin10:10:47

Will do, though I think Lanterna looks more promising since it's a bit more maintained. I think this is built from scratch and commits look a bit quiet

Kevin10:10:08

Also seems to be a work in progress and not on clojars

Kevin10:10:00

(seems to be another branch that's being worked on, by bad)

borkdude10:10:50

The code looks quite minimal, and minimal is good imo ;)

Kevin10:10:34

Very true

borkdude10:10:59

maybe it doesn't support mouse clicks etc

borkdude10:10:09

so maybe lantera is a better option in that regard

borkdude10:10:24

I'll keep an eye on your experimentation

borkdude10:10:31

or: feel free to keep me posted

Kevin13:10:59

Nice, good catch on the :text instead of :swing

Kevin14:10:52

How would you integrate something like this in bb? Would you have to write an impl like with jdbc?

borkdude14:10:56

yes, just check the other libraries that are included

Kevin16:10:43

Progress!

Kevin16:10:01

I'll continue on this after dinner, maybe open a WIP PR?

borkdude16:10:52

Sure. Can you make a feature toggle for it like in: https://github.com/borkdude/babashka/blob/master/doc/build.md#feature-flags and then turn it off by default?

borkdude16:10:45

We can turn it on after some experimentation, but I'm always conservative with adding things by default, since CI is memory constraint

borkdude16:10:57

Also binary size is something I monitor

borkdude16:10:14

But adding something under a feature flag is always good for a PR

Kevin17:10:11

It's also a question what we want to actually supply. the clojure-lanterna lib is a bit outdated and unmaintained(?)

Kevin17:10:38

There were some things in clojure-lanterna which didn't work with GraalVM. e.g. some functions read the file system I think, and resulted in com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: No instances of .FileChannelImpl

Kevin17:10:49

Not sure how to fix that in GraalVM

Kevin17:10:28

Reagent for Lanterna sounds like a fun project. On the otherhand we also have Membrane which could be an option

borkdude19:10:59

Isn't trikl intented as a reagent for the terminal kind of maybe?

Kevin08:10:29

Seems like it. I had some trouble getting it to work. Also I'm not sure why they need to open a port (I guess for remote sessions). I'm going to build a project using membrane for now and see how well it works

borkdude08:10:32

I asked in #lambdaisland. It's far from ready, it's more like a hobby status and no release on the horizon

borkdude08:10:01

probably forking lanterna and updating it might be a worthwhile effort

borkdude08:10:07

(clojure-lanterna)

Kevin08:10:43

Yeah that would be beneficial for sure. But contributing back might be a problem https://github.com/MultiMUD/clojure-lanterna/issues/24

Kevin08:10:47

We could have a graalvm compatible fork possibly. Then provide clojure-lanterna with bb, which I think would be a nice balance between low / high level functionality

Kevin08:10:53

Possibly fork to the babashka github org?

Kevin09:10:18

Thanks 👍

Kevin09:10:53

I'll update my bb branch to use this version, then create a WIP PR

Kevin12:10:40

Having trouble compiling a simple example with 3.0.3. I keep getting undefined reference to Java_java_util_prefs_FileSystemPreferences_chmod. Which might be related to this issue: https://github.com/mabe02/lanterna/issues/499

Kevin12:10:00

oh, hm. compiling with java8 graalvm does work :thinking_face:

borkdude12:10:01

@UG9U7TPDZ I also had this issue with 20.2.0 JDK 11 with the tetris repo, but it started working when I avoided the Swing stuff

borkdude12:10:24

So avoid all awt references

Kevin12:10:04

Ah I guess the DefaultTerminal class is automatically including swing stuff

Kevin12:10:16

That's a pain

borkdude12:10:30

Ah really, then maybe not use that class?

Kevin12:10:13

Yeah I'd have to look into it. Basically it does all the heavy lifting in terms of OS / terminal / capabilities

Kevin12:10:40

It figures out what you need related to your platform

borkdude12:10:50

OK, you can try to compile with 20.3.0-dev and see what happens

Kevin12:10:40

Cool thanks, installing now

Kevin12:10:19

Sadly that crashes with something new.

Fatal error:com.oracle.svm.core.util.VMError$HostedError: Option name "TruffleMultiThreaded" has multiple definitions: com.oracle.svm.truffle.api.SubstrateTruffleOptions.TruffleMultiThreaded and com.oracle.svm.truffle.api.SubstateTruffleOptions.TruffleMultiThreaded
	at com.oracle.svm.core.util.VMError.shouldNotReachHere(VMError.java:68)
	at com.oracle.svm.hosted.option.HostedOptionParser.collectOptions(HostedOptionParser.java:89)
	at com.oracle.svm.hosted.option.HostedOptionParser.<init>(HostedOptionParser.java:62)
	at com.oracle.svm.hosted.NativeImageGeneratorRunner.buildImage(NativeImageGeneratorRunner.java:238)
	at com.oracle.svm.hosted.NativeImageGeneratorRunner.build(NativeImageGeneratorRunner.java:509)
	at com.oracle.svm.hosted.NativeImageGeneratorRunner.main(NativeImageGeneratorRunner.java:115)
	at com.oracle.svm.hosted.NativeImageGeneratorRunner$JDK9Plus.main(NativeImageGeneratorRunner.java:541)
Error: Image build request failed with exit status 1
com.oracle.svm.driver.NativeImage$NativeImageError: Image build request failed with exit status 1
	at com.oracle.svm.driver.NativeImage.showError(NativeImage.java:1632)
	at com.oracle.svm.driver.NativeImage.build(NativeImage.java:1382)
	at com.oracle.svm.driver.NativeImage.performBuild(NativeImage.java:1343)
	at com.oracle.svm.driver.NativeImage.main(NativeImage.java:1302)
	at com.oracle.svm.driver.NativeImage$JDK9Plus.main(NativeImage.java:1814)

Kevin12:10:47

Can't find much about this

borkdude12:10:11

Are you using the reflector fix library?

borkdude12:10:20

That only works for a specific version

borkdude12:10:56

I'm not sure what you're compiling here

borkdude12:10:11

are you compiling bb or the fork of the lib?

borkdude12:10:15

or some other thing?

Kevin12:10:42

Compiling my own project, and yeah that was the problem. Still no results though. I'm just trying to get a minimal build going

Kevin12:10:51

I can host the code

borkdude12:10:07

OK. I'll be out for an hour and then I can take a look

Kevin13:10:21

https://github.com/kwrooijen/test-lanterna If getting defaultterminal to work then maybe it is a good idea to avoid it and all swing stuff (For now)

borkdude19:10:06

Sorry, had something else on my mind.

borkdude19:10:53

So you are trying to use lanterna 3 directly. Why not try to update the clojure-lanterna lib and e.g. build the tetris one?

borkdude19:10:16

It seems clojure-lanterna isn't using that class directly: https://github.com/MultiMUD/clojure-lanterna/blob/master/src/lanterna/terminal.clj#L1-L10 so maybe this is why compiling does work with that code

Kevin19:10:47

While trying to port clojure-laterna I noticed a lot of things are either renamed / deprecated. e.g. the Facade no longer exists. It felt a bit like a rabbit hole so I decided to first get a minimal snippet working

borkdude20:10:46

Is it possible to not use the DefaultTerminal but something like a TextTerminal instead?

Kevin20:10:28

Yeah that's probably the best option for now. Building my app right now so I'll get to it eventually, then move it to the babashka repo

Kevin19:10:01

Haven't tested Windows though

Kevin19:10:54

Hacking in the clojure-lanterna fork to get it to compile with bb

borkdude19:10:21

I think you have write access yourself, or do you want me to look at it?

Kevin19:10:15

It's just a WIP at the moment, but just to let you know. Going to make a PR for bb to include this (turned off by default)

borkdude19:10:42

excellent :)

borkdude20:10:22

Je kunt alvast wat CircleCI config neerzetten in die babashka lanterna repo. Ik kan dan een token aanmaken en zo wordt het automatisch gedeployed naar clojars.

borkdude20:10:57

Zie babashka.nrepl als voorbeeld

borkdude20:10:07

.circleci/config.yml etc

Kevin20:10:09

https://github.com/babashka/clojure-lanterna/pull/1/files#diff-1d37e48f9ceff6d8030570cd36286a61 Er zijn nog geen tests, waarschijnlijk kunnen er nog wat dingen uit de circleci file. Maar de belangrijkste lijkt me de deploy job?

borkdude20:10:10

Zijn er in de originele clojure lanterna ook geen tests?

Matias Francisco Hernandez Arellano13:10:22

Hi Folks.. New to clojure looking for some help

Matias Francisco Hernandez Arellano13:10:39

I'm trying to build a discord bot and in the example code of discljord defines a state variable

(def state (atom nil))
That is then used in the event handler
(defmethod handle-event :message-create
  [event-type {{bot :bot} :author :keys [channel-id content]}]
  (if (= content "!disconnect")
    (c/disconnect-bot! (:connection @state))
    (when-not bot
      (m/create-message! (:messaging @state) channel-id :content "Hello, World!"))))
I'm looking to create multiple event-handler each of those inside it's own namespace to be able to develop different commands that can be attached to the the bot ..., but not sure about the clojure way to pass that state variable across the namespaces. Should I just create the event handler and accept the state as argument? like
(defn get-on-brd-handler
  [event-type {{bot :bot} :author :keys [channel-id content]} state]
)

flowthing13:10:46

This question might be a better fit in #beginners, but you don’t need to pass it across namespaces. You can just reference it directly. For example:

(ns foo.bar)

(def state (atom nil))
And:
(ns bar.baz
  (:require [foo.bar :as bar]))

(defn handler
  [,,,]
  (do-something-with @bar/state))

emccue16:10:48

Is there any clojure/java equivalent to rails ActiveSupport::Inflector?

Lennart Buit21:10:34

There are pretty extensive libraries around everything natural language, icu4j springs to mind. Looking at the docs, it has pluralization.

Lennart Buit21:10:20

But; I guess thats something to consider when you have extensively translatable apps with dozens of languages 😛

Lennart Buit21:10:40

But yeah, some people have gone above and beyond in encoding natural language inconsistencies in every imaginable aspect 😛

emccue16:10:26

I just want to make some strings singular

noisesmith16:10:29

clojure.pprint/cl-format can do some of that

emccue16:10:13

cl-format can hold rules for it

emccue16:10:46

but specifically I am looking to name my sql tables plural and just have a result builder that changes :users/id to :user/id

noisesmith16:10:01

user=> (clojure.pprint/cl-format nil "~D tr~:@P/~D win~:P" 7 1)
"7 tries/1 win"

emccue16:10:40

yeah but that only works if I have a certain amount of times something appears in mind and also deconstruct the string into the format

emccue16:10:17

like I can manually write {"users" "user"} and do a mapping - but I'm looking to see if I can "solve" the problem

noisesmith16:10:50

given your actual use case, I suspect the amount of effort that would go into automating it exceeds the effort of just having a conversion layer that knows about each table

emccue16:10:38

Thats true, but there are "cool" and "fun" bars that only apply when unemployed and working on hobby projects

noisesmith16:10:25

also YMMV but many people consider that kind of magic conversion part of what's wrong with rails

dpsutton16:10:20

and one day you'll end up with a table called Librarys rather than the proper plural because its simpler than building an override mechanism to whatever you're doing

try-not-to-cry 3
fadrian16:10:02

I've got a result body coming out of a clj-http call that I'm needing to parse. I wrote a simple ANTLR grammar for the body that compiles using clj-antlr/parser with no issues. The problem comes when I try to actually apply the generated parser function to an input string. At this point, the parse errors out with the following message: Execution error (NullPointerException) at org.antlr.v4.runtime.atn.ATNSerializer/serialize (ATNSerializer.java:73). null. Any idea on how to even start debugging this? Or does anyone have a recommendation for a different parser library that is perhaps not so opaque?

p-himik16:10:23

Does the exception has any stack trace?

p-himik16:10:19

Instaparse doesn't handle ANTLR grammars.

fadrian16:10:49

Stack trace is as follows: [[org.antlr.v4.runtime.atn.ATNSerializer serialize ATNSerializer.java 73]   [org.antlr.v4.runtime.atn.ATNSerializer getSerialized ATNSerializer.java 601]   [org.antlr.v4.runtime.atn.ATNSerializer getSerializedAsChars ATNSerializer.java 605]   [org.antlr.v4.tool.Grammar createParserInterpreter Grammar.java 1337]   [clj_antlr.interpreted$singlethreaded_parser invokeStatic interpreted.clj 111]   [clj_antlr.interpreted$singlethreaded_parser invoke interpreted.clj 107]   [clj_antlr.interpreted.ThreadLocalParser parse interpreted.clj 119]   [clj_antlr.core.ParserWrapper parse core.clj 13]   [clj_antlr.core$parse_STAR_ invokeStatic core.clj 28]   [clj_antlr.core$parse_STAR_ invoke core.clj 19]   [clj_antlr.core$parse invokeStatic core.clj 35]   [clj_antlr.core$parse invoke core.clj 30]   [clj_antlr.core.ParserWrapper invoke core.clj 17]   [cql_p arser.core$parse_RAS_results invokeStatic form-init5406870106907233552.clj 822]   [cql_parser.core$parse_RAS_results invoke form-init5406870106907233552.clj 820]   [cql_parser.core$run_cql_file invokeStatic form-init5406870106907233552.clj 122]   [cql_parser.core$run_cql_file invoke form-init5406870106907233552.clj 109]   [cql_parser.core$run_cql_tests$fn__231 invoke core.clj 132]   [clojure.core$map$fn__5866 invoke core.clj 2755]   [clojure.lang.LazySeq sval LazySeq.java 42]   [clojure.lang.LazySeq seq LazySeq.java 51]   [clojure.lang.RT seq RT.java 535]   [clojure.core$seq__5402 invokeStatic core.clj 137]   [clojure.core$dorun invokeStatic core.clj 3133]   [clojure.core$doall invokeStatic core.clj 3148]   [clojure.core$doall invoke core.clj 3148]   [cql_parser.core$run_cql_tests invokeStatic core.clj 132]   [cql_parser.core$run_cql_tests invoke core.clj 128]   [cql_parser.core$_main invokeStatic core.clj 142]   [cql_parser.core$_main doInvoke core.clj 136]   [clojure.lang.RestFn invoke RestFn.java 397]   [c ql_parser.core$eval46620 invokeStatic form-init5406870106907233552.clj 1]   [cql_parser.core$eval46620 invoke form-init5406870106907233552.clj 1]   [clojure.lang.Compiler eval Compiler.java 7177]   [clojure.lang.Compiler eval Compiler.java 7132]   [clojure.core$eval invokeStatic core.clj 3214]   [clojure.core$eval invoke core.clj 3210]   [nrepl.middleware.interruptible_eval$evaluate$fn__944 invoke interruptible_eval.clj 91]   [clojure.main$repl$read_eval_print__9086$fn__9089 invoke main.clj 437]   [clojure.main$repl$read_eval_print__9086 invoke main.clj 437]   [clojure.main$repl$fn__9095 invoke main.clj 458]   [clojure.main$repl invokeStatic main.clj 458]   [clojure.main$repl doInvoke main.clj 368]   [clojure.lang.RestFn invoke RestFn.java 1523]   [nrepl.middleware.interruptible_eval$evaluate invokeStatic interruptible_eval.clj 84]   [nrepl.middleware.interruptible_eval$evaluate invoke interruptible_eval.clj 56]   [nrepl.middleware.interruptible_eval$interruptible_eval$fn__970$fn__974 invoke interruptible_eva l.clj 155]   [clojure.lang.AFn run AFn.java 22]   [nrepl.middleware.session$session_exec$main_loop__1071$fn__1075 invoke session.clj 190]   [nrepl.middleware.session$session_exec$main_loop__1071 invoke session.clj 189]   [clojure.lang.AFn run AFn.java 22]   [java.lang.Thread run Thread.java 745]]} CqlArithmeticFunctionsTest.cql #error {  :cause nil  :via  [{:type java.lang.NullPointerException    :message nil    :at [org.antlr.v4.runtime.atn.ATNSerializer serialize ATNSerializer.java 73]}]  :trace  [[org.antlr.v4.runtime.atn.ATNSerializer serialize ATNSerializer.java 73]   [org.antlr.v4.runtime.atn.ATNSerializer getSerialized ATNSerializer.java 601]   [org.antlr.v4.runtime.atn.ATNSerializer getSerializedAsChars ATNSerializer.java 605]   [org.antlr.v4.tool.Grammar createParserInterpreter Grammar.java 1337]   [clj_antlr.interpreted$singlethreaded_parser invokeStatic interpreted.clj 111]   [clj_antlr.interpreted$singlethreaded_parser invoke interpreted.clj 107]   [clj_antlr.interpreted.ThreadLocalParser parse interpreted.clj 119]   [clj_antlr.core.ParserWrapper parse core.clj 13]   [clj_antlr.core$parse_STAR_ invokeStatic core.clj 28]   [clj_antlr.core$parse_STAR_ invoke core.clj 19]   [clj_antlr.core$parse invokeStatic core.clj 35]   [clj_antlr.core$parse invoke core.clj 30]   [clj_antlr.core.ParserWrapper invoke core.clj 17]   [cql_p arser.core$parse_RAS_results invokeStatic form-init5406870106907233552.clj 822]   [cql_parser.core$parse_RAS_results invoke form-init5406870106907233552.clj 820]   [cql_parser.core$run_cql_file invokeStatic form-init5406870106907233552.clj 122]   [cql_parser.core$run_cql_file invoke form-init5406870106907233552.clj 109]   [cql_parser.core$run_cql_tests$fn__231 invoke core.clj 132]   [clojure.core$map$fn__5866 invoke core.clj 2755]   [clojure.lang.LazySeq sval LazySeq.java 42]   [clojure.lang.LazySeq seq LazySeq.java 51]   [clojure.lang.Cons next Cons.java 39]   [clojure.lang.RT next RT.java 713]   [clojure.core$next__5386 invokeStatic core.clj 64]   [clojure.core$dorun invokeStatic core.clj 3142]   [clojure.core$doall invokeStatic core.clj 3148]   [clojure.core$doall invoke core.clj 3148]   [cql_parser.core$run_cql_tests invokeStatic core.clj 132]   [cql_parser.core$run_cql_tests invoke core.clj 128]   [cql_parser.core$_main invokeStatic core.clj 142]   [cql_parser.core$_main doInvoke core.clj 136]   [clojure .lang.RestFn invoke RestFn.java 397]   [cql_parser.core$eval46620 invokeStatic form-init5406870106907233552.clj 1]   [cql_parser.core$eval46620 invoke form-init5406870106907233552.clj 1]   [clojure.lang.Compiler eval Compiler.java 7177]   [clojure.lang.Compiler eval Compiler.java 7132]   [clojure.core$eval invokeStatic core.clj 3214]   [clojure.core$eval invoke core.clj 3210]   [nrepl.middleware.interruptible_eval$evaluate$fn__944 invoke interruptible_eval.clj 91]   [clojure.main$repl$read_eval_print__9086$fn__9089 invoke main.clj 437]   [clojure.main$repl$read_eval_print__9086 invoke main.clj 437]   [clojure.main$repl$fn__9095 invoke main.clj 458]   [clojure.main$repl invokeStatic main.clj 458]   [clojure.main$repl doInvoke main.clj 368]   [clojure.lang.RestFn invoke RestFn.java 1523]   [nrepl.middleware.interruptible_eval$evaluate invokeStatic interruptible_eval.clj 84]   [nrepl.middleware.interruptible_eval$evaluate invoke interruptible_eval.clj 56]   [nrepl.middleware.interruptible_eval$interruptible_eval $fn__970$fn__974 invoke interruptible_eval.clj 155]   [clojure.lang.AFn run AFn.java 22]   [nrepl.middleware.session$session_exec$main_loop__1071$fn__1075 invoke session.clj 190]   [nrepl.middleware.session$session_exec$main_loop__1071 invoke session.clj 189]   [clojure.lang.AFn run AFn.java 22]   [java.lang.Thread run Thread.java 745]]

fadrian17:10:01

I've looked at Instaparse. If I can't get clj-antlr working (it has worked for me in previous projects), I'll start looking at alternatives. The language only has about a dozen productions so no matter how I do it, it'll eventually work - it's just that there are some complicated date-time formats to parse that I already have ANTLR code for.

p-himik17:10:33

Huh. I wonder how can data.add(atn.grammarType.ordinal()); throw an NPE given that none of the values are null.

fadrian17:10:10

@U0NCTKEV8 - I checked that out, too. I'm not using skip anywhere in the grammar as far as I know, which I believe is what the error in this case hinges on.

fadrian17:10:42

Here's the grammar, if that helps.

fadrian17:10:53

grammar RASResults; value     : tuple     | list     | interval     | literal     ; interval     : 'Interval' leftInterval literal ',' literal rightInterval      ; leftInterval     : '[' | '('     ; rightInterval     : ']' | ')'         ; list     : '[' valueList ']'     ; valueList     :     | value     | value ',' valueList     ; tuple         : 'Tuple' '{' pair '}'         | '{' STRING ':' STRING '}'         ; pair         : identifier '->' value         ; literal         : '{' '}'                                               #emptyListLiteral         | 'null'                                                #nullLiteral         | ('true' | 'false')                                    #booleanLiteral         | STRING                                                #stringLiteral         | NUMBER                                                #numberLiteral         | DATETIME                                              #dateTimeLiteral         | TIME                                                  #timeLiteral         ; identifier         : IDENTIFIER         ; /     Lexical rules */ DATETIME         : '@'             [0-9][0-9][0-9][0-9] // year             (                 (                     '-'[0-9][0-9] // month                     (                         (                             '-'[0-9][0-9] // day                             ('T' TIMEFORMAT?)?                         )                         | 'T'                     )?                 )                 | 'T'             )?             ('Z' | ('+' | '-') [0-9][0-9]':'[0-9][0-9])? // timezoneOffset         ; TIME         : '@' 'T' TIMEFORMAT         ; fragment TIMEFORMAT         : [0-9][0-9] (':'[0-9][0-9] (':'[0-9][0-9] ('.'[0-9]+)?)?)?         ; IDENTIFIER         : ([A-Za-z] | '')([A-Za-z0-9] | '')*            // Added _ to support CQL (FHIR could constrain it out)         ; STRING         : '"' (ESC | .)*? '"'         ; // Also allows leading zeroes now (just like CQL and XSD) NUMBER         : [0-9]+('.' [0-9]+)?         ; // Pipe whitespace to the HIDDEN channel to support retrieving source text through the parser. WS         : [ \r\n\t]+ -> channel(HIDDEN)         ; fragment ESC         : '\\' ([`'\\/fnrt] | UNICODE)    // allow \`, \', \\, \/, \f, etc. and \uXXX         ; fragment UNICODE         : 'u' HEX HEX HEX HEX         ; fragment HEX         : [0-9a-fA-F]         ;

hiredman17:10:09

but it points to it not being a clojure specific issue, but some issue with antlr, which means you'll have better luck asking people who know antlr, not people who know clojure

p-himik17:10:46

@U0DTU8J83 FWIW your grammar worked just fine for me with value being the root and with "{\"a\":\"b\"}" as an input.

fadrian17:10:04

I tried "49" as an input to get the error. But if you can parse that string, it may just be something in the NUMBER token that's screwing up. Let me try a few other inputs. Thanks for everyone's suggestions and assistance.

p-himik17:10:48

"49" also worked just fine for me.

p-himik17:10:39

Perhaps it's a version issue. I have clj-antlr 0.2.6 and antlr4 4.8-1.

fadrian17:10:42

That may be. I'm still using clj-antlr 0.2.5 and I'm not sure which version of ANTLR it's using. I'll make sure everything's updated. But you've given me a couple of leads. Thanks.

fadrian17:10:16

Upgrading clj-antlr to 0.2.6 seemed to do the trick. Thanks again, all.