@ wrote... (2 years, 4 months ago)

For various reasons I don't really like black even if it's very good at what it does.

The readability of a Django model with aligned fields vs after running black is a good example of why I'd never use it by default.

Anyhow…

It would be nice to use black occasionally on snippets of code from inside my editor (vim) but that almost never works as the first line is already indented.

For example:

# note  ꜜ space
$ echo " foo( )" | black -q -
 foo()
error: cannot format -: cannot use --safe with this file; failed to parse source file.  AST error message: unexpected indent (<unknown>, line 1)

# vs no leading space
$ echo "foo( )" | black -q -
foo()

So what to do? There doesn't appear to be any command line switch to say “ignore leading spaces” so I guess we need to wrap our call to black, enter black_wrap. I was tempted to call this tupac or drdre after some great rappers but I'm not that cool.

$ echo " foo( )" | black_wrap
 foo()
# leading space is preserved

Anyhow… here's the code.

#!/usr/bin/env python

"""
* read from stdin
* if there is leading whitespace then dedent it
* call black
* replace leading whitespace if applicable
"""

import sys
import textwrap
import subprocess


def dedented(text):
    indent_chars = ""
    _text = textwrap.dedent(text)

    for c in text:
        if c == _text[0]:
            break  # no longer whitespace
        else:
            indent_chars += c

    return _text, indent_chars


def black(text):
    cmd = ["black", "-q", "-"]
    proc = subprocess.run(cmd, input=text.encode(), capture_output=True, check=False)

    if proc.returncode != 0:
        sys.stderr.write(proc.stderr.decode())
        sys.exit(proc.returncode)

    return proc.stdout.decode()


def main():
    text = sys.stdin.read()
    _text, indent_chars = dedented(text)

    blacked = black(_text)
    blacked = textwrap.indent(blacked, indent_chars)

    sys.stdout.write(blacked)


if __name__ == "__main__":
    main()

I'm using pappasam/vim-filetype-formatter as my vim plugin to call out to black_wrap, here's a snippet of my .vimrc

Plugin 'pappasam/vim-filetype-formatter'
let g:vim_filetype_formatter_commands = {
      \ 'python': 'black_wrap',
      \ 'json': 'python -m json.tool --indent=2 -',
      \ }
nnoremap <silent> <f4> :FiletypeFormat<cr>
vnoremap <silent> <f4> :FiletypeFormat<cr>

So I just hit F4 then entire file is formatted via black or I can select some text (that might be in the middle of a function and already indented) and hit F4 and just those few lines get formatted.

Category: tech, Tags: python
Comments: 0
Click here to add a comment