beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
ericdallo 2021-02-23T02:28:04.210Z

@borkdude For some reason your function is failling for some cases, I could only reproduce with that test:

(is (= "(+ 1 1)\n" (#'f.file-management/replace-text "(+ 1 1)\n" "\n" 1 0 1 0)))
It gives a NPE Not sure it's the only issue though

grazfather 2021-02-23T02:32:11.210200Z

I ended up using a very similar when-let*

solf 2021-02-23T03:38:30.213Z

When generating a pom.xml using tools deps (`clojure -X:deps mvn-pom`), does it retrieve the version from somewhere? There doesn't seem to be something like a :version option in deps.edn.

seancorfield 2021-02-23T04:42:34.214Z

@dromar56 it generates a minimal pom.xml, it's possible that the mvn-pom exec function takes various keyword arguments... I don't know that they're documented...

seancorfield 2021-02-23T04:42:42.214200Z

Let me check the source code...

seancorfield 2021-02-23T04:44:51.215100Z

Hmm, I believe this is the function https://github.com/clojure/tools.deps.alpha/blob/f3a2fbb426ae65d5a5c79a756f3b95b463f334a4/src/main/clojure/clojure/tools/cli/api.clj#L88 -- so the only thing it current takes is :argmaps to help specify how to build the dependencies that appear in the pom.xml file.

seancorfield 2021-02-23T04:45:52.216300Z

If you are using depstar to build JAR files, it can update the pom.xml file for you -- it supports a :version exec argument, and will update pom.xml if you say :sync-pom true.

alexmiller 2021-02-23T04:53:32.216700Z

the version (like most things) should be set in the pom.xml and then deps will sync to it

alexmiller 2021-02-23T04:54:03.217500Z

the underlying pom gen/sync code does actually have the ability to set this, and I expect this to be part of other forthcoming work

1
solf 2021-02-23T04:56:49.217700Z

Ah thanks, I do use depstar, I'll switch to that

solf 2021-02-23T05:09:12.218300Z

Got it, thanks. I wasn't sure where was the idiomatic place to put the version, this answers it.

seancorfield 2021-02-23T05:09:41.219Z

@alexmiller I was actually a bit surprised when I looked at mvn-pom and it didn't seem to expose the group, artifact, or version that would be generated into the initial pom.xml...

seancorfield 2021-02-23T05:10:29.219400Z

(so I'm glad to hear that is a future possibility)

Pragyan Tripathi 2021-02-23T06:37:52.221400Z

I have a function defined in a namespace e.g., user/test-func I wish to send a keyword :user/test-func over network and resolve it into the defined function to execute. Is there a way to do it?

Pragyan Tripathi 2021-02-23T09:19:18.230700Z

I think this only works in clj... I am trying to do it in cljs..

phronmophobic 2021-02-23T06:44:18.223700Z

maybe something like:

user> (-> :clojure.string/join
          symbol
          requiring-resolve) 
#'clojure.string/join

Yang Xu 2021-02-23T06:49:06.226100Z

Hi, Who can explain this code to me? (:keyword things) What does this code do? I saw this code when invokes

; invoke
(driver.u/database->driver (:database preprocessed)
; defined
(def ^{:arglists '([database-or-id])} database->driver
  (memoize/ttl database->driver* :ttl/threshold 1000))
in Metabase source code. Does (:database preprcoessed) is a function invocation or just function parameters?

2021-02-23T06:51:06.227300Z

Keywords in Clojure behave like functions when used as the first element of an expression, and cause themselves to be looked up in a map. They aren't 100% the same in all ways, but (:kw foo) is similar in behavior to (get foo :kw)

Yang Xu 2021-02-23T06:53:57.228300Z

Thank you, I got it.

bhaim123 2021-02-23T08:22:28.230400Z

Hi I need to make println thread-safe. I sew an example using *out* but I was wondering if there is another option? What I need to do is to make sure my print is thread-safe, while other threads my print in their own way (which I cannot control)

Dave Suico 2021-02-23T10:20:21.232800Z

Hello World, Does anyone know an article/video/tutorial on how to work with google sheets? I am task to read/write sheets using clojure. Thank you!

robertfw 2021-02-23T10:23:49.232900Z

a quick bit of googling turned this up, though it is 5 years old. There is a comment from 2019 saying things were still working https://tech.metail.com/reading-google-sheets-from-clojure/

sb 2021-02-23T10:27:36.233300Z

Yes, robertfw link is great, plus if you would like to understand how could you generate a token and work with that.. good point this gist (I modified for my goals and worked fully .. in the last month) https://gist.github.com/arohner/8d94ee5704b1c0c1b206186525d9f7a7

2021-02-23T11:03:47.234Z

i use this, but it's still need login each time we run the code, i got stuck on the authentication token & refresh token...

(ns asdf.core
  (:require [clojure.edn :as edn]
            [clojure.test :refer :all]
            [google-apps-clj.google-drive :refer :all :as gdrive]
            [google-apps-clj.google-sheets-v4 :refer :all :as gsheets]
            [google-apps-clj.credentials :as gauth]))


(defn login
  []
  (gsheets/build-service "<http://1080463043693-gidvsert2tugasj4sdtt9odjjv3gledq.apps.googleusercontent.com|1080463043693-gidvsert2tugasj4sdtt9odjjv3gledq.apps.googleusercontent.com>"
                       "_la38KD6Hc73wPtwEAJZYOfJ"))

;this is just a test to connect
(login)

;here's the function to get the spreadsheet info/cell values
(gsheets/get-spreadsheet-info
 (login)
 "16qb3Zg4kdD62y7cf0oQ4TrRmq890V19fpnu7hmRpTeg")

(gsheets/get-cell-values
 (login)
 "16qb3Zg4kdD62y7cf0oQ4TrRmq890V19fpnu7hmRpTeg"
 ["Sheet1!B9:B11"])
the dependencies are these
[google-apps-clj "0.6.1"]]

2021-02-23T11:06:27.234200Z

@sb how to run that in a project?

sb 2021-02-23T11:09:49.234400Z

I reopen the project and I check it. One moment.

2021-02-23T11:14:00.234800Z

http://cool.no hurries, i'm thankful for that...

2021-02-23T11:17:50.235200Z

I'm using this https://github.com/SparkFund/google-apps-clj and i couldn't get the token & refresh token yet, been learning & working for the gsheet for about 1 week lol... i'm so frustrated here's my failure https://youtu.be/7GAcYNR6LFM

sb 2021-02-23T11:22:11.235500Z

@sfyire I check now the code. I used via lein-env the credentials : api-key and :creds {patht o service json …d49a5.json}. I downloaded from Google Dev console.

sb 2021-02-23T11:23:45.235700Z

and with that I can generate token like “ya27.c…. xYA4Haq”

sb 2021-02-23T11:24:38.235900Z

so no browser interaction

sb 2021-02-23T11:26:10.236100Z

(defn load-creds
  "Takes a path to a service account .json credentials file"
  [secrets-json-path]
  (-&gt; secrets-json-path slurp (json/parse-string keyword)))


(defn create-claim [creds &amp; [{:keys [sub] :as opts}]]
  (let [claim (merge {:iss   (:client_email creds)
                      :scope (str/join " " scopes)
                      :aud   "<https://www.googleapis.com/oauth2/v4/token>"
                      :exp   (-&gt; 1 time/hours time/from-now)
                      :iat   (time/now)}
                     (when sub
                       ;; when using the Admin API, delegating access, :sub may be needed
                       {:sub sub}))]
    (-&gt; claim jwt/jwt (jwt/sign :RS256
                                (-&gt; creds
                                    :private_key
                                    (#(StringReader. %)) (#(key/pem-&gt;private-key % nil))))
        (jwt/to-str))))


(defn request-token [creds &amp; [{:keys [sub] :as opts}]]
  (let [claim (create-claim creds opts)
        resp (http/post "<https://www.googleapis.com/oauth2/v4/token>"
                        {:form-params {:grant_type "urn:ietf:params:oauth:grant-type:jwt-bearer"
                                       :assertion  claim}
                         :as          :json})]
    (when (= 200 (-&gt; resp :status))
      (-&gt; resp :body :access_token))))

sb 2021-02-23T11:27:37.236300Z

(request-token (load-creds creds)) you get the token and you can use.. the v4 api and v3 api different.. eg

(http/post "<https://analytics.googleapis.com/$discovery/rest?version=v3>"
             {:accept           :json
              :throw-exceptions false
              :headers          {"Authorization" (str "Bearer " token)}
              :form-params      form-params
              :content-type     :json}))

sb 2021-02-23T11:28:11.236500Z

I used for analytics exports, but I think, that is similar to other things

2021-02-23T11:38:57.236700Z

let me digest this... sorry I'm still newbie ahahah

sb 2021-02-23T11:57:05.237200Z

(http/get (str "<https://sheets.googleapis.com/v4/spreadsheets/1L....iu3l4?key=>" api-key)
           {:accept           :json
            :headers          {"Authorization" (str "Bearer " token)}
            :content-type     :json})

sb 2021-02-23T11:58:18.237500Z

That is a simple GET request, you can test how to create post request via this documentation.. do you need add valid credentials too etc (allow this request type etc)

2021-02-23T12:06:20.237700Z

I'll try to run the code, and if there's any stuck question arises i'll try to ask... thanks a lot for sharing @sb

sb 2021-02-23T12:07:48.237900Z

Ok, the key the credentials and the scopes. I hope it will work for you!

🙏 1
Dave Suico 2021-02-23T16:03:43.238700Z

Thanks, everyone I appreciate all your response and now I ended up using an API key rather than looking for an official client library.:thumbsup:

2021-02-23T16:09:59.240200Z

hi, can someone using windows help, how to escape space character? sadly I couldn't avoid this

(create-claim
 (load-creds "/Users/Adrian Imanuel/Google Drive/3. Clojure/Projects/newgsheet/client_secret_1080463043693.json"))
i got an error due to using space
Execution error (NullPointerException) at java.io.StringReader/&lt;init&gt; (StringReader.java:50).
Cannot invoke "String.length()" because "s" is null

ghadi 2021-02-23T16:22:06.241100Z

type *e to see the full exception trace, and put that in a snippet here (Ctrl-Shift-Space)

ghadi 2021-02-23T16:22:45.241800Z

(I'm not sure I agree with the problem analysis around space characters)

2021-02-23T16:24:04.242Z

(ns newgsheet.core
  (:require [cemerick.url :as url]
            [cheshire.core :as json]
            [clj-jwt.core :as jwt]
            [clj-jwt.key :as key]
            [clj-time.core :as time]
            [clj-http.client :as http]
            [clojure.string :as str])
  (:import java.io.StringReader))

(defn request-token [creds &amp; [{:keys [sub] :as opts}]]
  (let [claim (create-claim creds opts)
        resp (http/post "<https://www.googleapis.com/oauth2/v4/token>"
                        {:form-params {:grant_type "urn:ietf:params:oauth:grant-type:jwt-bearer"
                                       :assertion  claim}
                         :as          :json})]
    (when (= 200 (-&gt; resp :status))
      (-&gt; resp :body :access_token))))

(request-token
 (load-creds "c:\\Users\\Adrian Imanuel\\Google Drive\\3. Clojure\\Projects\\newgsheet\\client_secret_1080463043693.json"))

ghadi 2021-02-23T16:26:04.243100Z

need the stack trace @adrianimanuel

2021-02-23T16:29:01.243500Z

here the stack trace

;; =&gt; #error {
 :cause "\\Users\\AdrianImanuel\\Google Drive\\3. Clojure\\Projects\\newgsheet\\client_secret_1080463043693.json (The system cannot find the path specified)"
 :via
 [{:type java.io.FileNotFoundException
   :message "\\Users\\AdrianImanuel\\Google Drive\\3. Clojure\\Projects\\newgsheet\\client_secret_1080463043693.json (The system cannot find the path specified)"
   :at [java.io.FileInputStream open0 "FileInputStream.java" -2]}]
 :trace
 [[java.io.FileInputStream open0 "FileInputStream.java" -2]
  [java.io.FileInputStream open "FileInputStream.java" 211]
  [java.io.FileInputStream &lt;init&gt; "FileInputStream.java" 153]
  [<http://clojure.java.io|clojure.java.io>$fn__11496 invokeStatic "io.clj" 229]
  [<http://clojure.java.io|clojure.java.io>$fn__11496 invoke "io.clj" 229]
  [<http://clojure.java.io|clojure.java.io>$fn__11409$G__11402__11416 invoke "io.clj" 69]
  [<http://clojure.java.io|clojure.java.io>$fn__11508 invokeStatic "io.clj" 258]
  [<http://clojure.java.io|clojure.java.io>$fn__11508 invoke "io.clj" 254]
  [<http://clojure.java.io|clojure.java.io>$fn__11409$G__11402__11416 invoke "io.clj" 69]
  [<http://clojure.java.io|clojure.java.io>$fn__11470 invokeStatic "io.clj" 165]
  [<http://clojure.java.io|clojure.java.io>$fn__11470 invoke "io.clj" 165]
  [<http://clojure.java.io|clojure.java.io>$fn__11422$G__11398__11429 invoke "io.clj" 69]
  [<http://clojure.java.io|clojure.java.io>$reader invokeStatic "io.clj" 102]
  [<http://clojure.java.io|clojure.java.io>$reader doInvoke "io.clj" 86]
  [clojure.lang.RestFn invoke "RestFn.java" 410]
  [clojure.lang.AFn applyToHelper "AFn.java" 154]
  [clojure.lang.RestFn applyTo "RestFn.java" 132]
  [clojure.core$apply invokeStatic "core.clj" 667]
  [clojure.core$slurp invokeStatic "core.clj" 6942]
  [clojure.core$slurp doInvoke "core.clj" 6942]
  [clojure.lang.RestFn invoke "RestFn.java" 410]
  [newgsheet.core$load_creds invokeStatic "form-init17689491352809040359.clj" 51]
  [newgsheet.core$load_creds invoke "form-init17689491352809040359.clj" 48]
  [newgsheet.core$eval7297 invokeStatic "form-init17689491352809040359.clj" 97]
  [newgsheet.core$eval7297 invoke "form-init17689491352809040359.clj" 96]
  [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__6180$fn__6181 invoke "interruptible_eval.clj" 87]
  [clojure.lang.AFn applyToHelper "AFn.java" 152]
  [clojure.lang.AFn applyTo "AFn.java" 144]
  [clojure.core$apply invokeStatic "core.clj" 665]
  [clojure.core$with_bindings_STAR_ invokeStatic "core.clj" 1973]
  [clojure.core$with_bindings_STAR_ doInvoke "core.clj" 1973]
  [clojure.lang.RestFn invoke "RestFn.java" 425]
  [nrepl.middleware.interruptible_eval$evaluate$fn__6180 invoke "interruptible_eval.clj" 87]
  [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__6211$fn__6215 invoke "interruptible_eval.clj" 152]
  [clojure.lang.AFn run "AFn.java" 22]
  [nrepl.middleware.session$session_exec$main_loop__6278$fn__6282 invoke "session.clj" 202]
  [nrepl.middleware.session$session_exec$main_loop__6278 invoke "session.clj" 201]
  [clojure.lang.AFn run "AFn.java" 22]
  [java.lang.Thread run "Thread.java" 832]]}

sb 2021-02-23T16:29:12.243700Z

before request-token missing three parts: load-creds, scopes, create-claim fns

;;; Example code for calling Google apis using a service account.

(defn load-creds
  "Takes a path to a service account .json credentials file"
  [secrets-json-path]
  (-&gt; secrets-json-path slurp (json/parse-string keyword)))

;; list of API scopes requested, e.g. <https://developers.google.com/admin-sdk/directory/v1/guides/authorizing>
(def scopes ["<https://www.googleapis.com/auth/admin.directory.user>"
             "<https://www.googleapis.com/auth/admin.directory.group>"])

(defn create-claim [creds &amp; [{:keys [sub] :as opts}]]
  (let [claim (merge {:iss (:client_email creds)
                      :scope (str/join " " scopes)
                      :aud "<https://www.googleapis.com/oauth2/v4/token>"
                      :exp (-&gt; 1 time/hours time/from-now)
                      :iat (time/now)}
                     (when sub
                       ;; when using the Admin API, delegating access, :sub may be needed
                       {:sub sub}))]
    (-&gt; claim jwt/jwt (jwt/sign :RS256 (-&gt; creds :private_key (#(StringReader. %)) (#(key/pem-&gt;private-key % nil)))) (jwt/to-str))))

2021-02-23T16:30:35.244600Z

@sb yes, sorry i'm cutting the code, it's my mistake, pls ignore that

sb 2021-02-23T16:30:48.245Z

ok

ghadi 2021-02-23T16:31:30.245900Z

ok cool: now that you have the stack trace (in the thread) you can read it like so:

ghadi 2021-02-23T16:33:51.248200Z

--------top of stack trace ----- opening a file that doesn't exist reading a file http://clojure.java.io/reader related frames clojure.core/slurp newgsheet.core/load-creds (this is the first part of your code that can be recognized) newgsheet.core$eval7297 (something you typed or evaluated in the REPL or editor) clojure/eval clojure.main/repl stuff nREPL crap for editor integration ---------outermost call -------

ghadi 2021-02-23T16:34:32.248700Z

you absolutely do not need any code to debug this @sb 🙂

👍 1
ghadi 2021-02-23T16:35:01.249400Z

call load-creds again, but make it (prn the-filename) before it slurp s the file

ghadi 2021-02-23T16:36:13.250500Z

You should be able to then call (slurp the-filename) and see the exact same error without your other code involved

2021-02-24T14:55:33.320100Z

@ghadi reporting that i figured out the missing private_key, it wasn't the code that has problem, but my generated json was the problem. but how did you figure out the missing :private_key? which part of the error stack tells? tbh I still couldn't read all of that

2021-02-23T16:41:18.251300Z

i change this :

(defn load-creds
  [secrets-json-path]
  (-&gt; secrets-json-path slurp (json/parse-string keyword)))
to
(defn load-creds
  [secrets-json-path]
  (-&gt; secrets-json-path prn (json/parse-string keyword)))
am i correct?

ghadi 2021-02-23T16:41:49.251600Z

no because prn returns nil

ghadi 2021-02-23T16:42:15.251800Z

what I want you to do is isolate the problem -- FileNotFound and reproduce it

2021-02-23T16:42:48.252Z

yes, got the points but didn't get the coding

ghadi 2021-02-23T16:43:20.252200Z

if you already know what is being passed in, you can do (slurp secrets-json-path) otherwise

(defn load-creds
  [secrets-json-path]
  (prn secrets-json-path) ;; add this
  (-&gt; secrets-json-path slurp (json/parse-string keyword)))

2021-02-23T16:46:59.252400Z

(load-creds "\\Users\\Adrian Imanuel\\Google Drive\\3. Clojure\\Projects\\newgsheet\\client_secret_1080463043693.json")
"\\Users\\Adrian Imanuel\\Google Drive\\3. Clojure\\Projects\\newgsheet\\client_secret_1080463043693.json"

;; =&gt; {:installed {:client_id "<http://1080463043693dtt9odjjv3gledq.apps.googleusercontent.com|1080463043693dtt9odjjv3gledq.apps.googleusercontent.com>", :project_id "clojuresheet-trial", :auth_uri "<https://accounts.google.com/o/oauth2/auth>", :token_uri "<https://oauth2.googleapis.com/token>", :auth_provider_x509_cert_url "<https://www.googleapis.com/oauth2/v1/certs>", :client_secret "_la38KD6ZYOfJ", :redirect_uris ["urn:ietf:wg:oauth:2.0:oob" "<http://localhost>"]}}
this is the result of above codes

ghadi 2021-02-23T16:48:21.252600Z

ok now that the string printed, does it look correct? does that file exist? the folder in between "Google Drive" and "Projects" looks suspicious

2021-02-23T16:48:23.252800Z

in my understanding, it's successfully read the path,

2021-02-23T16:48:53.253Z

the folder called "3. Clojure"

ghadi 2021-02-23T16:48:58.253200Z

yeah, it looks like it read the file

dpsutton 2021-02-23T16:49:20.253400Z

this comes up a few times but please be aware of the consequences of sharing your client_id and client secret for cloud services

➕ 1
2021-02-23T16:49:42.253700Z

no i already aware of that, i've edited/cut some characters

👍 1
ghadi 2021-02-23T16:50:19.254Z

ok if you call request-token on the result of load-creds, do you get the same error?

ghadi 2021-02-23T16:51:03.254200Z

Spoiler alert: all I'm going to suggest is to break the problem down systematically

2021-02-23T16:52:13.254400Z

;; =&gt; #error {
 :cause "Cannot invoke \"String.length()\" because \"s\" is null"
 :via
 [{:type java.lang.NullPointerException
   :message "Cannot invoke \"String.length()\" because \"s\" is null"
   :at [java.io.StringReader &lt;init&gt; "StringReader.java" 50]}]
 :trace
 [[java.io.StringReader &lt;init&gt; "StringReader.java" 50]
  [newgsheet.core$create_claim$fn__7268 invoke "form-init17689491352809040359.clj" 66]
  [newgsheet.core$create_claim invokeStatic "form-init17689491352809040359.clj" 66]
  [newgsheet.core$create_claim doInvoke "form-init17689491352809040359.clj" 54]
  [clojure.lang.RestFn invoke "RestFn.java" 410]
  [newgsheet.core$eval7327 invokeStatic "form-init17689491352809040359.clj" 211]
  [newgsheet.core$eval7327 invoke "form-init17689491352809040359.clj" 211]
  [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__6180$fn__6181 invoke "interruptible_eval.clj" 87]
  [clojure.lang.AFn applyToHelper "AFn.java" 152]
  [clojure.lang.AFn applyTo "AFn.java" 144]
  [clojure.core$apply invokeStatic "core.clj" 665]
  [clojure.core$with_bindings_STAR_ invokeStatic "core.clj" 1973]
  [clojure.core$with_bindings_STAR_ doInvoke "core.clj" 1973]
  [clojure.lang.RestFn invoke "RestFn.java" 425]
  [nrepl.middleware.interruptible_eval$evaluate$fn__6180 invoke "interruptible_eval.clj" 87]
  [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__6211$fn__6215 invoke "interruptible_eval.clj" 152]
  [clojure.lang.AFn run "AFn.java" 22]
  [nrepl.middleware.session$session_exec$main_loop__6278$fn__6282 invoke "session.clj" 202]
  [nrepl.middleware.session$session_exec$main_loop__6278 invoke "session.clj" 201]
  [clojure.lang.AFn run "AFn.java" 22]
  [java.lang.Thread run "Thread.java" 832]]}
oh no

ghadi 2021-02-23T16:52:50.254600Z

ok 🙂 so create-claim is barfing

ghadi 2021-02-23T16:53:25.254800Z

now we need more code @sb 🙂

sb 2021-02-23T16:53:52.255Z

https://gist.github.com/arohner/8d94ee5704b1c0c1b206186525d9f7a7 @ghadi that was the starter point

2021-02-23T16:54:25.255300Z

I'm sorry not being complete, thought i can pick some function because it has the same error, load-creds then create-claim then request-token

(ns newgsheet.core
  (:require [cemerick.url :as url]
            [cheshire.core :as json]
            [clj-jwt.core :as jwt]
            [clj-jwt.key :as key]
            [clj-time.core :as time]
            [clj-http.client :as http]
            [clojure.string :as str])
  (:import java.io.StringReader))

(defn load-creds
  "Takes a path to a service account .json credentials file"
  [secrets-json-path]
  (prn secrets-json-path)
  (-&gt; secrets-json-path slurp (json/parse-string keyword)))


(defn create-claim [creds &amp; [{:keys [sub] :as opts}]]
  (let [claim (merge {:iss   (:client_email creds)
                      :scope (str/join "<https://www.googleapis.com/auth/spreadsheets>")
                      :aud   "<https://www.googleapis.com/oauth2/v4/token>"
                      :exp   (-&gt; 1 time/hours time/from-now)
                      :iat   (time/now)}
                     (when sub
                       ;; when using the Admin API, delegating access, :sub may be needed
                       {:sub sub}))]
    (-&gt; claim jwt/jwt (jwt/sign :RS256
                                (-&gt; creds
                                    :private_key
                                    (#(StringReader. %)) (#(key/pem-&gt;private-key % nil))))
        (jwt/to-str))))



(defn request-token [creds &amp; [{:keys [sub] :as opts}]]
  (let [claim (create-claim creds opts)
        resp (http/post "<https://www.googleapis.com/oauth2/v4/token>"
                        {:form-params {:grant_type "urn:ietf:params:oauth:grant-type:jwt-bearer"
                                       :assertion  claim}
                         :as          :json})]
    (when (= 200 (-&gt; resp :status))
      (-&gt; resp :body :access_token))))

ghadi 2021-02-23T16:54:38.255500Z

I would try to lift out (-&gt; creds :private_key (#(StringReader. %)) (#(key/pem-&gt;private-key % nil)))) (jwt/to-str)) into the let binding

ghadi 2021-02-23T16:54:47.255700Z

it is very hard to understand

ghadi 2021-02-23T16:55:42.255900Z

from the trace above, StringReader is saying, "you gave me nil / null to read"

ghadi 2021-02-23T16:55:59.256200Z

so, is (-> creds :private_key) nil?

ghadi 2021-02-23T16:56:24.256400Z

(As a style note, don't mix -&gt; and #(....) )

ghadi 2021-02-23T16:57:40.256600Z

the creds being passed in to create-claim (coming from load-creds ) doesn't appear to have a :private_key

ghadi 2021-02-23T16:58:23.256800Z

I gotta run but: break your problems down and debug like a scientist, even if it's slow

2021-02-23T16:58:58.257Z

alright, i got the point now

2021-02-23T16:59:07.257200Z

will go slow, line by line

2021-02-23T17:00:51.257400Z

thanks a lot for the lesson @ghadi :confusedparrot: going to digest this

NoahTheDuke 2021-02-23T19:01:18.259100Z

given that I have two maps created from frequencies (so a map of string to the number of that string in the original collection), is there an easy way to subtract the second map from the first, creating a new map that has the subtracted total of the combined keys?

NoahTheDuke 2021-02-23T19:05:30.259700Z

i have a reduce call that does the trick, but it's nice to use built-ins if they exist

2021-02-23T19:07:05.259900Z

merge-with -

2021-02-23T19:09:00.262300Z

You probably want merge-with @nbtheduke. But I have a qn: how are you sure the keys will be the same? If there are different keys, how do you plan to handle it? If not, then the two maps must have been generated from the same input, is there a way you can generate it only once?

bhaim123 2021-02-23T19:13:36.262500Z

Hi I need to make `println` thread-safe. I sew an example using `*out*` but I was wondering if there is another option? What I need to do is to make sure my print is thread-safe, while other threads my print in their own way (which I cannot control)

alexmiller 2021-02-23T19:17:42.262700Z

println is thread safe

alexmiller 2021-02-23T19:17:57.263100Z

so what behavior are you seeking?

alexmiller 2021-02-23T19:18:13.263500Z

do you mean "not interleaved with other threads printing"?

grazfather 2021-02-23T19:18:29.263700Z

with-out-str?

bhaim123 2021-02-23T19:30:30.264900Z

I would like to send a long text to println and make sure the whole text is printed without any other massing the string.

alexmiller 2021-02-23T19:31:11.265400Z

the easiest way is to make the string, then println the full string (don't let println make it)

alexmiller 2021-02-23T19:31:52.266200Z

(println (str/join " " [my args to println])) instead of (println my args to println)

bhaim123 2021-02-23T19:32:13.266500Z

Got it, thanks a lot!

alexmiller 2021-02-23T19:32:39.266900Z

println will then just print the string to stdout and flush, should not be any interleaving

👍 2
seancorfield 2021-02-23T19:35:52.268600Z

That's not quite sufficient, since println prints the string and the newline separately so you can still get some interleaving. I've found that to completely avoid potential interleaving, you need to do (print (str (str/join " " [my args to println]) "\n"))

👀 1
seancorfield 2021-02-23T19:36:40.269500Z

(it's an edge case so you won't encounter it unless you're printing at a high rate from multiple threads)

bhaim123 2021-02-23T19:38:43.270700Z

Thanks, that is the case I have. This is the service startup where many threads write at the same time in high rate for a few good seconds

2021-02-23T19:51:20.270900Z

That will work well if a key appears in both maps. If it only occurs in the second, I think it will give you the value from the second map. If you want negative of the value from the second map, you'll need a different way.

💯 1
2021-02-23T19:51:48.271100Z

(it also works well if a key appears in only the first map)

NoahTheDuke 2021-02-23T20:01:41.272300Z

@hiredman and @thomastayac_slack meant to respond sooner but got called away. those are both good answers, but i determined i need to exclude negative numbers, so the reduce call ended up being the best method

👍 1
👀 1
Eric Ihli 2021-02-23T21:33:46.274900Z

How do you change the in-repl representation of a custom data type? I was hoping there's an interface that provides something like Pythons __repr__ or Clojures Object (toString [_] ,,,) but that REPLS use when displaying a value?

alexmiller 2021-02-23T21:41:21.275700Z

you can extend print-method for that (but it's preferred to print as Clojure data)

🙏 1
blak3mill3r 2021-02-23T21:51:26.275900Z

So that's why newlines get interleaved when printlning from many threads. Thanks for elucidating

blak3mill3r 2021-02-23T21:51:48.276100Z

That's a bit of a nasty behavior

seancorfield 2021-02-23T22:12:59.276400Z

Yeah, it surprised me when I first encountered it... 👀