How to best integrate zprint as pre-commit hook?


#1

Formatting code before committing is probably the easiest way to integrate a tool like zprint into a team’s workflow. In cljdoc we’re now adding Prettier and they provide a few ways of integrating it into a pre-commit hook.

I know that a few members of this forum are also using zprint to format files. Do you integrate that via a pre commit hook? If so I’d love to hear more about how you do it.

This thread is somewhat related but more focused on editor integrations than pre-commit hooks.


I need some zprint config examples
#2

Interesting topic! A number of communities - Go, Elm, and now with prettier, JavaScript - have moved to improve workflows using autoformatting. I think the Clojure community would benefit from more widespread use. My 2 cents:

In Clojure, there are currently two options:

  • cljfmt has a light touch. AFAIK it mostly fixes indentation and normalizes newlines. But it doesn’t do more significant changes, such as breaking a line in two or merging two non-empty lines.
  • The newer zprint reformats more aggressively. Essentially it disregards all the current formatting and reformats it in a standard way. This is a more powerful concept, as you can (in principle) write an entire file in a single line and then let zprint worry about formatting.

Each approach has its advantages. Personally I think zprint is good enough to enable for the entire codebase, but it’s a harder sell in a team setting than cljfmt.

I think zprint is a wonderful piece of software with plenty of customization options.

To integrate with tooling, we have a few complementary options:

  1. You can set up CI to reject commits that don’t pass the “fix” mode of cljfmt or zprint. Developers then need to manually rerun cljfmt fix, commit and push.
  2. You can set up your editor to automatically reformat the file when you save it. (In emacs writing an Emacs modes that does this would be trivial.)
  3. You can instruct developers to set up a pre-push hook (.git/hooks/pre-push) that auto-fixes the formatting and creates a formatting commit. (A pre-commit hook would be an option as well.)

Whether options 2 and 3 are going to be pleasant to use depends on the speed of the operation. This again depends on both the startup time and the time to reformat files. For example, if a pre-push commit adds >2-3 seconds on each push, people will be less likely to use it.

The author of zprint has spent a considerable amount of time exploring options of making the tool start up fast. I haven’t spent enough time to say if a graalvm-compiled version of zprint is fast enough. For instance, I haven’t found a pre-compiled binary for MacOS yet. But this would be a good option to explore.


#3

You unfortunately still have to build it yourself (hopefully that will change) but the startup is very snappy.

The files in the cljdoc repo aren’t very big but times range from 30ms for 15loc to 400ms for 350loc.

In another project two different 1000loc files take 700ms and 2000ms.

(Note that this is plain zprint, I remember „hang mode“ being good but expensive and I’m not sure if it’s enabled by default.)

I think if you combine this with a pre-commit hook that only runs on Clojure files reported in git diff --name-only --staged it could end up quite reasonable speed-wise. Some of the other pretty printers have options to only run on changed lines but not sure how feasible this would be with zprint.


#4

I’ve been meaning to open a thread about auto formatters for some time.

For our team differences in formatting in our editors have caused us quite a bit of pain over the months. People on our team are using Emacs (mainly Spacemacs), Cursive and Vim. Having done Elm previously, I really loved how you can sloppily write the code and it would format itself.

On zprint vs cljfmt: we’ve tried both and gone back and forth between them and are currently back with cljfmt. We couldn’t get ourselves to apply zprint to our whole project, as there were too many cases where it didn’t look right. The options zprint offers are more of a downside here. I believe a auto formatter should come without any configuration options.

If others are interested, I’d love to explore if we can move towards having an auto formatter without options work well. When this came up previously on slack, both @colinfleming and @alexmiller agreed this would be a nice thing to have. I think it would lower the barrier to entry for Clojure significantly. Looking at our attempts with zprint and how much work this was for other languages I’m also a bit scared.

Regarding how to run it: I think a native image with Graal VM is the way to go. We currently have the formatter be part of our system so we can use ciders format functions (cider-format-defun/region/buffer). Maybe offering both could be best from an integration perspective?


I need some zprint config examples
#5

cljfmt can also be compiled using graalvm. We use this on our project and it takes between 3-4 seconds to check the entire project (and can complete in under 1s if you only check the files that are different from master branch).

There’s a few versions with different options:




#6

New releases of zprint now come with pre-built binaries for Mac and Linux: https://github.com/kkinnear/zprint/issues/66


#7

Yes, starting with zprint 0.4.11, there are pre-compiled graalvm binaries for both MacOS and Linux. On my old mid-2012 MacBook Air, the MacOS one will start up in 55ms. You can find them here. The MacOS one is zprintm-0.4.11 and the Linux one is zprintl-0.4.11.

I’m a bit late to the party here in this thread, but if people are having trouble with getting zprint to make things look “right”, I’d be happy to help figure out how to configure it to do it “right”. I’m also quite interested in finding out what zprint doesn’t do well and fixing it. It is still certainly a work in progress.

Regarding an unconfigurable source formatter for Clojure(script). I would be willing to offer an unconfigurable version of zprint if there was strong agreement on what the output should look like. But that’s probably a topic for another thread.


#8

With zprint binaries being lightning fast now (great work @kkinnear!), I couldn’t resist the temptation and made a bare-bones minor mode for Emacs called zprint-mode.el

Please give it a spin. Caveat: this is my first Emacs mode, so guidance on how to do things properly is appreciated (@plexus?).

The elisp embeds a Bash script that automatically downloads and caches the appropriate graalvm binary - that script might be a useful base if anyone is implementing a similar on-save hook for Atom or other editors.


#9

Looks good to me! You could tidy it a tiny bit up with region-beginning and region-end

https://www.gnu.org/software/emacs/manual/html_node/elisp/The-Region.html


#10

If you’re not afraid of Unix sorcery (big If, I know) and you have the zprint-filter script in your path, you can use the following one-liner to reformat all the .clj(s) files in the local git repository:

git ls-files -z '*.cljs' '*.clj' | xargs -0 -I '{}' bash -c 'set -eu;f="$1"&&shift;c="$1"&&shift;t=$(mktemp)&&function x(){ rm -f "$t";}&&trap "x" EXIT;"$c" "[email protected]"<"$f">"$t";cp "$t" "$f"' -- '{}' zprint-filter

#11

I just signed up to host an “unsession” on zprint at the Clojure Conj 2018. The presentation part will be minimal (just to bring folks up to speed a bit), but I’m hoping to have some discussions about how people would like to use zprint, what features they want, and where it should go in the future. Please sign up on the wiki page: https://github.com/clojureconj/clojureconj2018/wiki/Unsessions if you are interested.

I’ll report back here to a new topic in Clojureverse whatever comes up in the session (well, unsession) to include the folks that aren’t at the Conj into the discussion.

Thanks!