rewrite-clj

ericdallo 2021-11-25T00:11:15.165Z

Any tips on how to sort a map node by its keys alphabetically with rewrite-clj?

ericdallo 2021-11-25T00:12:03.165100Z

Like:

(-> (z/of-string "{:b 1 :a 2}")
     my-sort
     z/sexpr)
=> {:a 2 :b 1}

lread 2021-11-25T04:24:51.165400Z

Should not be crazy hard.

lread 2021-11-25T04:25:29.165600Z

Can take a look sometime my-tomorrow, if you like

lread 2021-11-25T04:26:59.165800Z

Have you considered what you want to do with reader discards #_?

lread 2021-11-25T04:27:48.166Z

And maps can be unbalanced in rewrite-clj. Any thoughts there?

borkdude 2021-11-25T07:39:33.166500Z

And … namespaced maps

ericdallo 2021-11-25T12:19:36.166800Z

Yeah, it'd be necessary to handle those case I think

ericdallo 2021-11-25T12:19:56.167Z

Is there any other easier way to sort that if not using rewrite-clj?

lread 2021-11-25T14:14:43.167200Z

You might also have to think about formatting. Does sorting also reformat? Like if you have:

{:b 2 :c 3
 :a 1}
What result would you expect?

lread 2021-11-25T14:15:20.167400Z

And what would you expect to happen for nested maps? Just immediate children sorted?

lread 2021-11-25T14:18:49.167600Z

I think I'd use rewrite-clj if I were to do this, but... maybe I am biased?

ericdallo 2021-11-25T14:24:08.167800Z

I would expect to keep the same sort, follow the same formatting before applying the function

ericdallo 2021-11-25T14:24:37.168Z

> I think I'd use rewrite-clj if I were to do this, but... maybe I am biased? hahah, I think rewrite-clj makes sense for that, but It doesn't seems easy at least to me

lread 2021-11-25T14:25:20.168200Z

You could decide not to sort if there are #_ or the map is unbalanced.

👍 1
lread 2021-11-25T14:32:20.168500Z

The preserving formatting might be more complicated. I would encourage you to work out exactly what you'd like to see for various inputs. For example, exactly what should this look like after sorted?:

{:x 2 :c 2        :d 4
      :a 12345678 :y 1}
If you are not careful, you might end up scoping in some sort of vertical alignment feature.

lread 2021-11-25T14:40:36.168800Z

But if you get too restrictive and only allow sorting on maps that look like:

{:z 2
 :a 1 
 :x 2
 :c 3}
Maybe a sort map feature is only marginally better than an editor/IDE's sort lines feature?

lread 2021-11-25T14:41:12.169Z

I guess I'm suggesting some hammock before coding.

ericdallo 2021-11-25T15:09:54.169300Z

yeah, the idea is to add a code action on clojure-lsp that sort map keys, so I'd like to avoid changing formatting and things like that, only sort/swap the key-value pair with other key-value pair

ericdallo 2021-11-25T15:10:19.169500Z

not sure it'd be a big issue if the result map become not correctly formatted, I think for now would be ok

ericdallo 2021-11-25T15:10:41.169700Z

later then we could use cljfmt format after sorting when the vertical alignment is implemented :) (if user opt-in to that)

borkdude 2021-11-25T15:11:06.170Z

I have the same issue with rewrite-edn. it doesn't always do nice formatting, but at least all the original whitespace and comments are there

👍 1
borkdude 2021-11-25T15:12:27.170300Z

what's the use case for sorting map keys? I've never had this desire

ericdallo 2021-11-25T15:14:04.170500Z

https://github.com/clojure-lsp/clojure-lsp/issues/651 I do think it's something cool to have as a command, not priority tho

ericdallo 2021-11-25T15:14:32.170800Z

I already saw that feature request on other libs like clj-refactor, and other editors

borkdude 2021-11-25T15:16:39.171Z

cool

lread 2021-11-25T15:18:14.171300Z

Well, I'm happy to fiddle up a little simplistic/naive example sometime later today for you.

❤️ 1
lread 2021-11-25T23:43:36.174400Z

I’ve started to fiddle a bit late in the day… will continue tomorrow… Interesting… but… well.. we’ll see…

lread 2021-11-26T01:37:39.174600Z

Hmm… I’ve got something kinda working… but there are many nuances to explore.

👀 1
lread 2021-11-26T16:27:23.174900Z

So, the tricky part is not find the keys and values in the map. The tricky part, I think, is deciding what to do with spacing, comments and discards. Let’s start with basics, just in case there are lurkers who are interested. If we go all naive, and ignore the possibility of #_, here’s an example of finding all keys in a map:

(->> "{:a 1
       :b 2
       :c 3}"
     z/of-string
     z/down
     (iterate #(-> % z/right z/right))
     (take-while identity)
     (map z/string))
;; => (":a" ":b" ":c")
As you can see, rewrite-clj stores child nodes of a map in a simple sequence. It does not distinguish the key from the value. Here’s the same, but for values:
(->> "{:a 1
       :b 2
       :c 3}"
     z/of-string
     z/down
     z/right
     (iterate #(-> % z/right z/right))
     (take-while identity)
     (map z/string))
;; => ("1" "2" "3")

lread 2021-11-26T16:34:03.175100Z

So it is not too terribly difficult to find keys and values, and one found, sort by the stringified key. But what what to do about: • spacing • comments • #_

lread 2021-11-26T16:50:24.175300Z

For spacing, given kv-seq <k><spacing>+<v>, I am currently experimenting with moving kv-seq and leaving any surrounding whitespace where it is. This seems to work fine for simple cases:

{:c 3 
 :b 2 
 :a 1}
becomes, as you would expect:
{:a 1
 :b 2
 :c 3}
And with this strategy
{    :d 4
    :c 3
   :b 2
  :a 1 }
Becomes:
{    :a 1
    :b 2
   :c 3
  :d 4 }
But this currently can break down a bit if a kv-seq includes newlines. For example:
{:z 1 :b {:x 4
          :c 7}
 :a [1 2
     3 7]}
Becomes (you’ll notice that :c is no longer aligned under :x, and you will notice that nested maps are not automatically sorted):
{:a [1 2
     3 7] :b {:x 4
          :c 7}
 :z 1}
I suppose though, we could add in specific compensations for these cases. But first, I’d like your feedback on this general approach.

lread 2021-11-26T17:00:39.175500Z

I’ve not looked a comments yet, but let’s explore a scenario:

{;; c1 is this a comment for the map or for the first kv?
 :z 1 ;; c2 this is most probably a comment for this kv and should move with it.
 ;; c3 is this a comment for a group of kvs or only the next kv, or is it a continuation of the comment for the last kv?
 :a 2
 :x 3}
I think if a comment trails a value on the same line, it is pretty clear it should be moved with the kv-seq. For the rest… well… not sure what we should do. Maybe just leave them where they are and let the user move them after the sort? Also notice that a trailing comment, if moved to the last element in the map, will mean moving the closing } , the above might become:
{;; c1 is this a comment for the map or for the first kv?
 :a 2
 ;; c3 is this a comment for a group of kvs or only the next kv, or is it a continuation of the comment for the last kv?
 :x 3
 :z 1 ;; c2 this is most probably a comment for this kv and should move with it.
}

lread 2021-11-26T17:01:56.175700Z

For maps containing #_ and unbalanced maps, I think maybe our original idea of not sorting is good.

lread 2021-11-26T17:05:12.176100Z

This is also an option for maps containing comments.

ericdallo 2021-11-26T17:15:18.176700Z

Thank you for the help @lee! > Becomes (you’ll notice that `:c` is no longer aligned under `:x`, and you will notice that nested maps are not automatically sorted): I'm pretty ok on this minor alignment for now, users could just call clojure-lsp format after the sort or even clojure-lsp could do that automatically for that map when user triggers the sort :) > Maybe just leave them where they are and let the user move them after the sort? The same, I'm pretty ok on leaving comments not on the same line of a k-v not sorted

ericdallo 2021-11-26T17:16:55.176900Z

I think most formatting cases we could fix with calling format for that map right after the sort.

lread 2021-11-26T17:28:19.177100Z

Cool, I’ll explore moving the same-line comment, do a little cleanup and share what I’ve come up with.

1
lread 2021-11-26T21:54:48.177400Z

@ericdallo, the code was getting a bit long so I instead of a gist, http://nextjournal.com/lread/sorting-maps-by-key-with-rewrite-clj. Pretty cool! Lemme know watcha think and if this gets you on your way. If not, we can chat more.

ericdallo 2021-11-27T00:42:18.177600Z

wow, what a detailed explanation @lee, that's awesome!

ericdallo 2021-11-27T00:43:06.177800Z

thank you very much for the effort, also explaining it on nextjournal was very cool

lread 2021-11-27T00:43:38.178Z

Glad you are enjoying it! I'm not crazy about my excessive use of loop but not a bad first crack at it.

lread 2021-11-27T00:44:01.178200Z

Yeah, nextjournal is pretty awesome.

ericdallo 2021-11-27T00:44:02.178400Z

I understood the concepts of the function and I will even need to use the sortable-map? as well 😛

ericdallo 2021-11-27T00:44:38.178600Z

I'll try to use on clojure-lsp and let you know if I find any issues, but it looks really good as a first iteration

lread 2021-11-27T00:45:40.178800Z

Cool, you might want to add a few more tests as I only really touched the surface.

ericdallo 2021-11-27T00:46:07.179Z

Sure, I'll add tests on clojure-lsp side you should tweet about that article 🙂

lread 2021-11-27T00:46:36.179200Z

Meh, I'm not much of a tweeter.

lread 2021-11-27T00:47:13.179400Z

I'm in it for the fun, not the fame. 🙂

ericdallo 2021-11-27T00:47:55.179600Z

haha, yeah I meant, people would love to see those explanations and code

lread 2021-11-27T00:50:55.179800Z

Go ahead and tweet it if you like. I think I've only tweeted like 4 times. Don't want to get into the habit!

ericdallo 2021-11-27T00:51:49.180Z

haha, I will tweet later and tag you (if you don't mind)

ericdallo 2021-11-27T00:52:07.180200Z

Also, I hope you don't mind if I co-author you the clojure-lsp commit

lread 2021-11-27T00:52:16.180400Z

Ya, sure, sounds good.

👍 1
ericdallo 2021-11-27T01:21:53.180700Z

@lee it works like a charm 🚀

ericdallo 2021-11-27T01:24:59.181100Z

c/c @nate

lread 2021-11-27T03:26:20.181300Z

coolio!

nate 2021-11-27T04:18:40.182Z

Oh wow!! Thank you for implementing this so quickly!

👍 2
borkdude 2021-11-25T15:28:08.171800Z

gratitude rewrite-clj is such a great project gratitude

lread 2021-11-25T15:30:11.172900Z

Yeah, gotta agree, pragmatic and fun!

borkdude 2021-11-25T15:31:10.173500Z

@ericdallo btw, Eric, I managed to do without making a change in rewrite-clj with regards to adding an id to the keyword nodes

borkdude 2021-11-25T15:32:06.173900Z

Just adding a :context with an id to the node in the hook is sufficient. https://github.com/clj-kondo/clj-kondo/pull/1469/files#diff-78dfd2ff457fbf1d75e85a7e3949bbf31bc199636bb192e2e79d759a03d4a4c6R24

ericdallo 2021-11-25T16:06:50.174200Z

oh, that's nice!