kkinnear / zprint

Executables, uberjar, and library to beautifully format Clojure and Clojurescript source code and s-expressions.
MIT License
547 stars 46 forks source link

Making exceptions for `{:force-nl? true}` in maps when destructuring #315

Open filip-gomore opened 4 months ago

filip-gomore commented 4 months ago

In most cases I want maps to have a newline after every key value pair so I have set {:map {:force-nl? true}}.

In my codebase I frequently have (defn pipeline-step [{:keys [a b c] :as state}] ...) and would like to have maps in arglists (and ideally maps on the left hand side in bindings generally) have a different configuration, i.e. leave these in one line if possible. I was not able to come up with a config that makes the relevant exception. Is this possible?

kkinnear commented 4 months ago

Interesting problem, thanks for asking! I can solve the argument vector one for you pretty simply. The left hand side of binding vectors is a lot harder. I don't see a way to do that at present, but I'll work on it some more. I want to give you what I have for argument vectors now, though, so you don't have to wait for that.

Basically, you just tell zprint that when a map is inside a vector, you should have {:map {:force-nl? false}}. This will work, of course, if you don't havre lots of maps in vectors elsewhere in your code. I could construct something that would only do this for maps in vectors at the top level of a defn, but that is more complex and hopefully not necessary. Let me know if it is.

Here is how you would do this:

; The basic test, showing what you normally get

(czprint i315 {:parse-string? true :map {:force-nl? true}})
(defn test-maps
  ([{{:keys [nl-separator? respect-nl?]} :pair,
     :as options} ind zloc-seq caller]
   (let [{:a :b,
          :c :d}
           this
         {:e :f,
          :g :h}
           {:is :a,
            :real :test}
         sowhat (about {:this :map,
                        :and :newlines})]
     sowhat)))

;
; Here is the way you fix the argument vectors
;

(czprint i315
         {:parse-string? true,
          :map {:force-nl? true},
          :vector {:option-fn (fn
                                ([] "vector-option-fn")
                                ([opts n exprs] {:map {:force-nl? false}}))}})
(defn test-maps
  ([{{:keys [nl-separator? respect-nl?]} :pair, :as options} ind zloc-seq
    caller]
   (let [{:a :b,
          :c :d}
           this
         {:e :f,
          :g :h}
           {:is :a,
            :real :test}
         sowhat (about {:this :map,
                        :and :newlines})]
     sowhat)))

This fixes the argument vector. It doesn't fix the other maps in the test expression I have created. I'll work on that.

If this doesn't work for you, please let me know and we'll see what we can do to fix it up. Again, thanks for asking!

kkinnear commented 4 months ago

I've been thinking about how to handle the maps in let bindings and elsewhere. There really isn't any way to configure the left-hand-side of bindings. I keep coming up with major architectural changes to support something like that. While that may be necessary, I want to go in a different direction first.

Can you tell me where the maps are that you do want to have {:map {:force-nl? true}} in force? I think it might be easier to work out how to turn that on where you want it, then to turn it off where you don't want it. At least I'd like to work from that direction and see what I can figure out before I create some massive architectural change in configuration.

So, where, specifically, do you want to have {:map {:force-nl? true}} enabled?

filip-gomore commented 4 months ago

Sorry for only getting back to you now. Thanks so much for all your suggestions!

The maps we wanted to have force-nl? true are the ones we use as values. I guess destructuring is the only exception we have. At least I can't come up with any others right now.

The example you provided for arglists already helps a lot because this covers most of our cases! Thanks again :bow:

kkinnear commented 4 months ago

I'm still a bit confused. When you say "the ones we use as values", what specifically do you mean? That is, where are the values? Are you talking about values in let bindings? Or as values in other maps, where you have "key-value" pairs? Or both, or other places too? I think I might be able to make something work without major changes, but I need the details or I will build something that doesn't really solve your problem. Thanks!

filip-gomore commented 4 months ago

Oh yes I phrased this weirdly. I just meant plain old maps that don't have special meaning in the syntax. We're trying to make it work for maps that are returned from functions, passed as arguments or assigned to a binding or a var and nested maps in all literal data structures too. So it'd be really all maps except those that are on the LHS in bindings or function signatures I'd say.