180 lines
6.6 KiB
Python
180 lines
6.6 KiB
Python
# pyflyby/_format.py.
|
|
# Copyright (C) 2011, 2012, 2013, 2014 Karl Chen.
|
|
# License: MIT http://opensource.org/licenses/MIT
|
|
|
|
from __future__ import (absolute_import, division, print_function,
|
|
with_statement)
|
|
|
|
import six
|
|
|
|
|
|
class FormatParams(object):
|
|
max_line_length = 79
|
|
wrap_paren = True
|
|
indent = 4
|
|
hanging_indent = 'never'
|
|
use_black = False
|
|
|
|
def __new__(cls, *args, **kwargs):
|
|
if not kwargs and len(args) == 1 and isinstance(args[0], cls):
|
|
return args[0]
|
|
self = object.__new__(cls)
|
|
# TODO: be more careful here
|
|
dicts = []
|
|
for arg in args:
|
|
if arg is None:
|
|
pass
|
|
elif isinstance(arg, cls):
|
|
dicts.append(arg.__dict__)
|
|
else:
|
|
raise TypeError
|
|
if kwargs:
|
|
dicts.append(kwargs)
|
|
for kwargs in dicts:
|
|
for key, value in six.iteritems(kwargs):
|
|
if hasattr(self, key):
|
|
setattr(self, key, value)
|
|
else:
|
|
raise ValueError("bad kwarg %r" % (key,))
|
|
return self
|
|
|
|
|
|
def fill(tokens, sep=(", ", ""), prefix="", suffix="", newline="\n",
|
|
max_line_length=80):
|
|
r"""
|
|
Given a sequences of strings, fill them into a single string with up to
|
|
``max_line_length`` characters each.
|
|
|
|
>>> fill(["'hello world'", "'hello two'"],
|
|
... prefix=("print ", " "), suffix=(" \\", ""),
|
|
... max_line_length=25)
|
|
"print 'hello world', \\\n 'hello two'\n"
|
|
|
|
:param tokens:
|
|
Sequence of strings to fill. There must be at least one token.
|
|
:param sep:
|
|
Separator string to append to each token. If a 2-element tuple, then
|
|
indicates the separator between tokens and the separator after the last
|
|
token. Trailing whitespace is removed from each line before appending
|
|
the suffix, but not from between tokens on the same line.
|
|
:param prefix:
|
|
String to prepend at the beginning of each line. If a 2-element tuple,
|
|
then indicates the prefix for the first line and prefix for subsequent
|
|
lines.
|
|
:param suffix:
|
|
String to append to the end of each line. If a 2-element tuple, then
|
|
indicates the suffix for all lines except the last, and the suffix for
|
|
the last line.
|
|
:return:
|
|
Filled string.
|
|
"""
|
|
N = max_line_length
|
|
assert len(tokens) > 0
|
|
if isinstance(prefix, tuple):
|
|
first_prefix, cont_prefix = prefix
|
|
else:
|
|
first_prefix = cont_prefix = prefix
|
|
if isinstance(suffix, tuple):
|
|
nonterm_suffix, term_suffix = suffix
|
|
else:
|
|
nonterm_suffix = term_suffix = suffix
|
|
if isinstance(sep, tuple):
|
|
nonterm_sep, term_sep = sep
|
|
else:
|
|
nonterm_sep = term_sep = sep
|
|
lines = [first_prefix + tokens[0]]
|
|
for token, is_last in zip(tokens[1:], [False]*(len(tokens)-2) + [True]):
|
|
suffix = term_suffix if is_last else nonterm_suffix
|
|
sep = (term_sep if is_last else nonterm_sep).rstrip()
|
|
# Does the next token fit?
|
|
if len(lines[-1] + nonterm_sep + token + sep + suffix) <= N:
|
|
# Yes; add it.
|
|
lines[-1] += nonterm_sep + token
|
|
else:
|
|
# No; break into new line.
|
|
lines[-1] += nonterm_sep.rstrip() + nonterm_suffix + newline
|
|
lines.append(cont_prefix + token)
|
|
lines[-1] += term_sep.rstrip() + term_suffix + newline
|
|
return ''.join(lines)
|
|
|
|
|
|
def pyfill(prefix, tokens, params=FormatParams()):
|
|
"""
|
|
Fill a Python statement.
|
|
|
|
>>> print(pyfill('print ', ["foo.bar", "baz", "quux", "quuuuux"]), end='')
|
|
print foo.bar, baz, quux, quuuuux
|
|
>>> print(pyfill('print ', ["foo.bar", "baz", "quux", "quuuuux"],
|
|
... FormatParams(max_line_length=15, hanging_indent='auto')), end='')
|
|
print (foo.bar,
|
|
baz,
|
|
quux,
|
|
quuuuux)
|
|
>>> print(pyfill('print ', ["foo.bar", "baz", "quux", "quuuuux"],
|
|
... FormatParams(max_line_length=14, hanging_indent='auto')), end='')
|
|
print (
|
|
foo.bar,
|
|
baz, quux,
|
|
quuuuux)
|
|
|
|
:param prefix:
|
|
Prefix for first line.
|
|
:param tokens:
|
|
Sequence of string tokens
|
|
:type params:
|
|
`FormatParams`
|
|
:rtype:
|
|
``str``
|
|
"""
|
|
N = params.max_line_length
|
|
if params.wrap_paren:
|
|
# Check how we will break up the tokens.
|
|
len_full = sum(len(tok) for tok in tokens) + 2 * (len(tokens)-1)
|
|
if len(prefix) + len_full <= N:
|
|
# The entire thing fits on one line; no parens needed. We check
|
|
# this first because breaking into lines adds paren overhead.
|
|
#
|
|
# Output looks like:
|
|
# from foo import abc, defgh, ijkl, mnopq, rst
|
|
return prefix + ", ".join(tokens) + "\n"
|
|
if params.hanging_indent == "never":
|
|
hanging_indent = False
|
|
elif params.hanging_indent == "always":
|
|
hanging_indent = True
|
|
elif params.hanging_indent == "auto":
|
|
# Decide automatically whether to do hanging-indent mode. If any
|
|
# line would exceed the max_line_length, then do hanging indent;
|
|
# else don't.
|
|
#
|
|
# In order to use non-hanging-indent mode, the first line would
|
|
# have an overhead of 2 because of "(" and ",". We check the
|
|
# longest token since even if the first token fits, we still want
|
|
# to avoid later tokens running over N.
|
|
maxtoklen = max(len(token) for token in tokens)
|
|
hanging_indent = (len(prefix) + maxtoklen + 2 > N)
|
|
else:
|
|
raise ValueError("bad params.hanging_indent=%r"
|
|
% (params.hanging_indent,))
|
|
if hanging_indent:
|
|
# Hanging indent mode. We need a single opening paren and
|
|
# continue all imports on separate lines.
|
|
#
|
|
# Output looks like:
|
|
# from foo import (
|
|
# abc, defgh, ijkl,
|
|
# mnopq, rst)
|
|
return (prefix + "(\n"
|
|
+ fill(tokens, max_line_length=N,
|
|
prefix=(" " * params.indent), suffix=("", ")")))
|
|
else:
|
|
# Non-hanging-indent mode.
|
|
#
|
|
# Output looks like:
|
|
# from foo import (abc, defgh,
|
|
# ijkl, mnopq,
|
|
# rst)
|
|
pprefix = prefix + "("
|
|
return fill(tokens, max_line_length=N,
|
|
prefix=(pprefix, " " * len(pprefix)), suffix=("", ")"))
|
|
else:
|
|
raise NotImplementedError
|