This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-05-12
Channels
- # announcements (4)
- # babashka (93)
- # beginners (43)
- # calva (56)
- # cherry (4)
- # clerk (58)
- # clj-otel (4)
- # cljs-dev (1)
- # clojure (31)
- # clojure-conj (1)
- # clojure-dev (30)
- # clojure-europe (88)
- # clojure-india (2)
- # clojure-italy (3)
- # clojure-nl (1)
- # clojure-norway (17)
- # clojure-uk (2)
- # clojurescript (5)
- # clr (13)
- # conjure (2)
- # cursive (4)
- # data-science (4)
- # datalevin (1)
- # fulcro (3)
- # gratitude (7)
- # hyperfiddle (27)
- # kaocha (3)
- # lsp (9)
- # malli (6)
- # nbb (1)
- # off-topic (41)
- # pedestal (2)
- # practicalli (1)
- # rdf (3)
- # re-frame (3)
- # reitit (10)
- # releases (1)
- # shadow-cljs (8)
- # testing (3)
- # vim (2)
- # xtdb (7)
I noticed an issue with the script I wrote where once I provide a command line argument of a path that has whitespace in it (like "/path/to/Goodnotes 5/my-file.pdf") , it looks first at "/path to/Goodnotes" and then it looks at "5/my-file.pdf", and finds neither files and throws an exception 😅 As a newbie in debugging Clojure code, and babashka code in particular, my first reaction was to try and tweak the script to receive that same path "hard-coded" instead of receiving it as a command line argument, and try to fix the bug through inspection etc. However, there's a part of me that's curious about if there are other ways to go about doing that. Particularly, I've seen that babashka book showcases test files and test running. Is this something that I should look into? Alternatively, should I be using some Cursive features that I'm currently not? (I'm just running a REPL and gradually sending stuff to it to check if it works, or inspect stuff)
If I were to be able to use a debugger, in the way that I debug code for other languages, I would be placing a breakpoint inside the function that receives the path, but I'm unfamiliar with Cursive debugger, and also not even sure if I should be using that.
For reference, this is my script here: https://github.com/amitnovick/bookmark-files-combiner/blob/main/src/bookmark_files_combiner/core.clj
> As a newbie in debugging Clojure code, and babashka code in particular, my first reaction was to try and tweak the script to receive that same path "hard-coded" instead of receiving it as a command line argument, and try to fix the bug through inspection etc. Yes, I think this approach is good. Just call your function with the input which triggers the error in the REPL and then narrow down the issue.
@U04V15CAJ I believe that the issue stems from this line here or some subsequent line: https://github.com/amitnovick/bookmark-files-combiner/blob/main/src/bookmark_files_combiner/core.clj#L134
Is there a way to save the previous result before continuing to perform the rest of the function?
I'm referring to the already generated values inside the let form. I'd want to be able to do part of the content in it, and use the vars again
if you haven't already I recommend reading this: https://clojure.org/guides/repl/introduction
the purpose is that you can write your REPL expressions in there and evaluate them from your editor, without executing them when the program is called
Oh so you mean people use those comment
forms just so they don't have to clean it up afterwards?
because I was gonna do the work outside the let clause and just put it back in there and remove the def
s after that
@U04V15CAJ I don't quite understand how comment
helps me for debugging
for reference, I'm now trying to debug this function here that gets called from the let
clause
(defn merge-pdfs [output-combined-pdf-path pdf-files]
(shell (str/join " " ["pdftk",
(str/join " "
(map
(fn [pdf-file-path]
(str pdf-file-path)
)
pdf-files
)),
"cat",
"output",
output-combined-pdf-path
]))
)
it's right here: https://github.com/amitnovick/bookmark-files-combiner/blob/main/src/bookmark_files_combiner/core.clj#L94
Now I wish to evaluate the (str/join) form inside the function, once it gets invoked (by being sent to the REPL)
In other languages, I would just put a debugger on a line inside the function and evaluate the values at that point in time
So in the end I just changed the inside of the function so that instead of calling the shell immediately, I just return the value of (str/join) and was able to inspect it afterwards. Turns out the bug fix was that I had to add single-quotes to every file name to avoid whitespace being interpreted by the shell as separate arguments. The change was a one-liner, so I just copied it, reset my git back to previous state and pasted it in :rolling_on_the_floor_laughing:
> So in the end I just changed the inside of the function so that instead of calling the shell immediately, I just return the value of (str/join) and was able to inspect it afterwards. 👍 > Turns out the bug fix was that I had to add single-quotes to every file name to avoid whitespace being interpreted by the shell as separate arguments. 👎
I already told you many times that you can supply separate string arguments to shell. There is no need to escape those with single quotes, just do not concatenate them :)
(defn merge-pdfs [output-combined-pdf-path pdf-files]
(apply shell "pdftk" ["file1" "file2" "file3"]))
I think something like this should work:
(defn merge-pdfs [output-combined-pdf-path pdf-files]
(apply shell "pdftk" (concat (map str pdf-files) ["cat" "output" output-combined-pdf-path])))
I think map str
isn't even necessary since shell
will already call str
on the arguments
@U04V15CAJ your code works 🙂
I tried to embrace what you taught and made another version kinda like yours:
(defn merge-pdfs [output-combined-pdf-path pdf-files]
(shell "pdftk",
(vector pdf-files),
"cat",
"output",
output-combined-pdf-path
)
)
you are passing this to shell
:
(shell "pdftk" [....] "cat" "output" output-combined-pdf-path)
Hovering on shell
in my editor gives me the docs:
> [...] The first command line argument is automatically tokenized
Does tokenized means adding the single-quotes on path strings?
and if so, is it really only the first argument? because there is "pdftk", "file1", "file2" "cat" "output", then first argument "file1" is tokenized? but about what bout "file2" is that also tokenized?
only the first argument to shell
is tokenized, e.g.: (shell "ls -la")
=> (shell "ls" "-la")
this is just so you can easily copy things between bash and shell
. you don't have to use that feature
yes it is because what you did is concatenate everything to one big string and then feed that as the only string to shell
ok, if you write (shell "foo bar 'foo bar'")
this executes the same as (shell "foo" "bar" "foo bar")
because shell pulls apart the first string
and you constructed one big string like (shell (str/join " " files))
so shell first tokenizes (pulls apart) that big string into separate strings
So the shell function received my long string and then pulled it apart on the whitespaces?
yes. if you write:
(shell "rm my file")
this will run the same as (shell "rm" "my" "file")
but if you already have "rm" and "my file" there is no need to make that into a single string, just pass them both:
(shell "rm" "my file")
Thx for shelling out tutorial!
is this a pattern that i should expect to work? this currently fails
user> (spit (fs/create-temp-file) "foo")
this works, but it reads awkwardly to convert a file to a file
user> (spit (.toFile (fs/create-temp-file)) "foo")
(spit (fs/file (fs/create-temp-file)) "foo")
Yes, unfortunately clojure 1.11 does not support java.nio.file.Path
in spit
, but this might change in 1.12 (cc @U064X3EF3 😉 )