Fork me on GitHub
#beginners
<
2024-02-19
>
Jim Newton12:02:02

I'd like to use the function browse-url from clojure.java.browse to browse a file on my local file system. How can I convert a file name to a url which will work on *nix and also on windows? Normally the file name has been created by a call to .File/createTempFile.

Jim Newton12:02:27

Here is some similar code in Scala, but it seems to use a different java library.

for {path_to_file <- str_to_png(gv, title)}
    locally {
      import java.awt.Desktop
      import java.net.URI
      if (Desktop.isDesktopSupported) {
        // See: 
        Desktop.getDesktop.browse(java.nio.file.Paths.get(path_to_file).toUri)
      }
    }

Jim Newton12:02:32

If I simply try to use (browse-url "file:///path/name/to/file.svg") it tries to start an application called LaTeXiT rather than opening in the browser

Jim Newton12:02:21

when I look at the code for browse-url I see it calls the function open-url-in-browser whose code is the following, which looks a lot like the Scala code I posted above.

(defn- open-url-in-browser
  "Opens url (a string) in the default system web browser.  May not
  work on all platforms.  Returns url on success, nil if not
  supported."
  [url]
  (try 
    (when (clojure.lang.Reflector/invokeStaticMethod "java.awt.Desktop" 
      "isDesktopSupported" (to-array nil))
      (-> (clojure.lang.Reflector/invokeStaticMethod "java.awt.Desktop" 
            "getDesktop" (to-array nil))
        (.browse (URI. url)))
      url)
    (catch ClassNotFoundException e
      nil)))        

Jim Newton12:02:59

maybe I should call open-url-in-browser directly rather than going through browse-url ?

Jim Newton12:02:05

when I try to do that, I see that the function is private

1. Unhandled java.lang.IllegalAccessError
   open-url-in-browser is not public

                  core.clj: 4250  clojure.core/refer
                  core.clj: 4218  clojure.core/refer
               RestFn.java:  139  clojure.lang.RestFn/applyTo
                  core.clj:  669  clojure.core/apply

Bob B14:02:56

browse uses the underlying platform to 'launch' the path provided. I think the result is that, at this point in the timeline of OSes, that'll generally open the path in whatever application it's associated with.

1
delaguardo15:02:09

you can call "private" function by using its fully qualified name: (#'clojure.java.browse/open-url-in-browser url)

1
sheluchin16:02:21

How would you convert a hex string to its decimal representation? "0x2d3f4304d303d49fe26a" -> 213673465822491589993066

sheluchin16:02:30

(BigInteger. "2d3f4304d303d49fe26a" 16)) ; 213673465822491589993066
Thanks, @U0NCTKEV8. Is that about as good as I can get? I have to strip out the leading 0x from the string myself?

hiredman16:02:52

It depends, as good as for what? The leading 0x indicates what follows is a hex number, and which is useful for people and general parsers, but if you already know it is a hex number it is not needed

hiredman16:02:51

The general constructor from a string that doesn't take a radix may work with the 0x, but it will also parse non-hex numbers which you may not want

dumrat16:02:44

How to do a raw multi line string in Clojure?

Noah Bogart17:02:02

There are no raw strings in clojure. You can put a newline in a regular string tho, if that’s what you want.

sheluchin21:02:20

Is there a better construct for conditionally assoc'ing values into a map?

(cond-> {}
  true (assoc :foo 1)
  false (assoc :bar 2)
  true (assoc :baz 3)))

vraid21:02:11

How about this?

(into {}
      (->> [[true [:foo 1]]
            [false [:bar 2]]
            [true [:baz 3]]]
           (filter first)
           (map second)))

tomd22:02:39

For me, there's no better construct than yours @UPWHQK562. IMO @U0552GV2X32’s approach is less clear, but that may depend on your familiarity with cond->. If you want to create a macro to reduce the repetition of assoc you could do something like this:

(defmacro assoc-some-> [expr & clauses]
  (let [steps (mapcat (fn [[t k v]]
                        `(~t (assoc ~k ~v)))
                      (partition 3 clauses))]
    `(cond-> ~expr ~@steps)))

(assoc-some-> {}
  true :foo 1
  false :bar 2
  true :baz 3)
;; => {:foo 1, :baz 3}
but I'd recommend against it. It breaks the first rule of macro club: don't write macros. It's just more syntax for everyone reading your code to know, and it's not worth saving the space. Maybe fun to play with though 🙂

vraid22:02:20

I'd agree that mine is less readable for explicit lists, but it will handle the case where the list is unknown before runtime, if that's what you're working with. I should have been clearer about that. E.g like this

(into {}
      (->> some-list
           (filter first)
           (map second)))

tomd22:02:10

Ah yup, fair point. I'd probably transduce those intermediate colls away, but agree the approach is good for runtime:

(into {} (keep (fn [[b kv]] (when b kv))) some-list)

vraid22:02:45

Ah, that's better. I wasn't familiar with the keep syntax

sheluchin01:02:21

Thanks for the replies! I'm aware of the transducer options but I think cond-> stands out as the easiest (in terms of reading) solution.

👍 1
phronmophobic01:02:05

Just as alternative, I often use merge, but I think cond-> is probably still preferable:

(merge
 {:default "options"}
 (when foo?
   {:foo 42})
 (when bar?
   {:bar 43}))

sheluchin01:02:14

I like that option too. That's quite legible, but I haven't seen it used anywhere so the familiarity of cond-> tips the scales IMO.

👍 1
Ben Sless06:02:04

Your original form is most readable and has best performance if you use transients

adi09:02:20

assoc is just a function, so I copied and modified its source as follows, to preserve the semantics of assoc. It appears to work.

(defn assoc-when
  ([m pred k v]
   (if pred (clojure.lang.RT/assoc m k v) m))
  ([m pred k v & kvs]
   (let [ret (if pred (clojure.lang.RT/assoc m k v) m)]
     (if kvs
       (if (nnext kvs)
         (recur ret (first kvs) (second kvs) (nth kvs 2) (nthnext kvs 3))
         (throw (IllegalArgumentException.
                 "assoc-when expects modulo-three number of arguments after map/vector, found another number")))
       ret))))

user=> (assoc-when {} true :foo 1)
{:foo 1}

user=> (assoc-when {} false :foo 1)
{}

user=> (assoc-when {} false :foo 1 true :bar)
Execution error (IllegalArgumentException) at user/assoc-when (REPL:1).
assoc-when expects modulo-three number of arguments after map/vector, found another number

user=> (assoc-when {} false :foo 1 true :bar 2)
{:bar 2}

user=> (assoc-when {} true :foo 1 false :bar 2 true :baz 3)
{:foo 1, :baz 3}

user=> (assoc-when {} true :foo 1 true :bar 2 true :baz 3)
{:foo 1, :bar 2, :baz 3}

user=> (assoc-when [] true 0 1 true 1 2 true 2 3)
[1 2 3]

sheluchin14:02:03

Thank you for adding on, @UK0810AQ2 and @U051MHSEK. I like that assoc-when fn. My main purpose here is to conditionally build up a map. I suppose with this fn I could drop the cond-> (which, although not very esoteric, is still somewhat less familiar) and replace it with a simple ->.

👍 2
adi05:02:50

Okay, based on the shape of your code, I assumed you were going for a general "assoc-when" concept. It seems like you need to consider one set of pred, key, value at a time? In which case, this will suffice.

(defn maybe-assoc
  [m [pred k v]]
   (if pred (assoc m k v) m))
And now you can...
(reduce maybe-assoc {} [[true :foo 1], [false :bar 2], ...])