Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CASSANDRA-19150 cqlsh: Left align text and right align numbers #3182

Draft
wants to merge 1 commit into
base: trunk
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pylib/cqlshlib/cqlshmain.py
Original file line number Diff line number Diff line change
Expand Up @@ -1022,7 +1022,7 @@ def print_formatted_result(self, formatted_names, formatted_values, with_header,

# print row data
for row in formatted_values:
line = ' | '.join(col.rjust(w, color=self.color) for (col, w) in zip(row, widths))
line = ' | '.join(col.just(w, color=self.color) for (col, w) in zip(row, widths))
self.writeresult(' ' + line)

if tty:
Expand Down
18 changes: 15 additions & 3 deletions pylib/cqlshlib/displaying.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from enum import Enum
from collections import defaultdict

RED = '\033[0;1;31m'
Expand All @@ -27,12 +28,17 @@
ANSI_RESET = '\033[0m'


def colorme(bval, colormap, colorkey):
class Alignment(Enum):
LEFT = 1
RIGHT = 2


def colorme(bval, colormap, colorkey, alignment=None):
if colormap is NO_COLOR_MAP:
return bval
if colormap is None:
colormap = DEFAULT_VALUE_COLORS
return FormattedValue(bval, colormap[colorkey] + bval + colormap['reset'])
return FormattedValue(bval, colormap[colorkey] + bval + colormap['reset'], alignment=alignment)


def get_str(val):
Expand All @@ -43,7 +49,7 @@ def get_str(val):

class FormattedValue:

def __init__(self, strval, coloredval=None, displaywidth=None):
def __init__(self, strval, coloredval=None, displaywidth=None, alignment=Alignment.RIGHT):
self.strval = strval
if coloredval is None:
coloredval = strval
Expand All @@ -53,6 +59,7 @@ def __init__(self, strval, coloredval=None, displaywidth=None):
# displaywidth is useful for display of special unicode characters
# with
self.displaywidth = displaywidth
self.alignment = alignment

def __len__(self):
return len(self.strval)
Expand All @@ -63,6 +70,11 @@ def _pad(self, width, fill=' '):
else:
return ''

def just(self, width, fill=' ', color=False):
if self.alignment == Alignment.LEFT:
return self.ljust(width, fill, color)
return self.rjust(width, fill, color)

def ljust(self, width, fill=' ', color=False):
"""
Similar to self.strval.ljust(width), but takes expected terminal
Expand Down
49 changes: 26 additions & 23 deletions pylib/cqlshlib/formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

from cassandra.cqltypes import EMPTY
from cassandra.util import datetime_from_timestamp
from .displaying import colorme, get_str, FormattedValue, DEFAULT_VALUE_COLORS, NO_COLOR_MAP
from .displaying import colorme, get_str, FormattedValue, DEFAULT_VALUE_COLORS, NO_COLOR_MAP, Alignment

UNICODE_CONTROLCHARS_RE = re.compile(r'[\x00-\x1f\x7f-\xa0]')
CONTROLCHARS_RE = re.compile(r'[\x00-\x1f\x7f-\xff]')
Expand Down Expand Up @@ -65,7 +65,7 @@ def format_by_type(val, cqltype, encoding, colormap=None, addcolor=False,
if nullval is None:
nullval = default_null_placeholder
if val is None:
return colorme(nullval, colormap, 'error')
return colorme(nullval, colormap, 'error', alignment=Alignment.LEFT)
if addcolor is False:
colormap = empty_colormap
elif colormap is None:
Expand All @@ -80,7 +80,7 @@ def format_by_type(val, cqltype, encoding, colormap=None, addcolor=False,
boolean_styles=boolean_styles)


def color_text(bval, colormap, displaywidth=None):
def color_text(bval, colormap, displaywidth=None, alignment=None):
# note that here, we render natural backslashes as just backslashes,
# in the same color as surrounding text, when using color. When not
# using color, we need to double up the backslashes, so it's not
Expand All @@ -95,7 +95,7 @@ def color_text(bval, colormap, displaywidth=None):
coloredval = colormap['text'] + bits_to_turn_red_re.sub(tbr, bval) + colormap['reset']
if colormap['text']:
displaywidth -= bval.count(r'\\')
return FormattedValue(bval, coloredval, displaywidth)
return FormattedValue(bval, coloredval, displaywidth, alignment)


DEFAULT_NANOTIME_FORMAT = '%H:%M:%S.%N'
Expand Down Expand Up @@ -209,7 +209,7 @@ def format_value_default(val, colormap, **_):
val = str(val)
escapedval = val.replace('\\', '\\\\')
bval = CONTROLCHARS_RE.sub(_show_control_chars, escapedval)
return bval if colormap is NO_COLOR_MAP else color_text(bval, colormap)
return bval if colormap is NO_COLOR_MAP else color_text(bval, colormap, alignment=Alignment.RIGHT)


# Mapping cql type base names ("int", "map", etc) to formatter functions,
Expand Down Expand Up @@ -249,46 +249,46 @@ def __str__(self):
@formatter_for('BlobType')
def format_value_blob(val, colormap, **_):
bval = '0x' + val.hex()
return colorme(bval, colormap, 'blob')
return colorme(bval, colormap, 'blob', alignment=Alignment.LEFT)


formatter_for('bytearray')(format_value_blob)
formatter_for('buffer')(format_value_blob)
formatter_for('blob')(format_value_blob)


def format_python_formatted_type(val, colormap, color, quote=False):
def format_python_formatted_type(val, colormap, color, quote=False, alignment=None):
bval = str(val)
if quote:
bval = "'%s'" % bval
return colorme(bval, colormap, color)
return colorme(bval, colormap, color, alignment)


@formatter_for('Decimal')
def format_value_decimal(val, float_precision, colormap, decimal_sep=None, thousands_sep=None, **_):
if (decimal_sep and decimal_sep != '.') or thousands_sep:
return format_floating_point_type(val, colormap, float_precision, decimal_sep, thousands_sep)
return format_python_formatted_type(val, colormap, 'decimal')
return format_python_formatted_type(val, colormap, 'decimal', alignment=Alignment.RIGHT)


@formatter_for('UUID')
def format_value_uuid(val, colormap, **_):
return format_python_formatted_type(val, colormap, 'uuid')
return format_python_formatted_type(val, colormap, 'uuid', alignment=Alignment.LEFT)


formatter_for('timeuuid')(format_value_uuid)


@formatter_for('inet')
def formatter_value_inet(val, colormap, quote=False, **_):
return format_python_formatted_type(val, colormap, 'inet', quote=quote)
return format_python_formatted_type(val, colormap, 'inet', quote=quote, alignment=Alignment.LEFT)


@formatter_for('bool')
def format_value_boolean(val, colormap, boolean_styles=None, **_):
if boolean_styles:
val = boolean_styles[0] if val else boolean_styles[1]
return format_python_formatted_type(val, colormap, 'boolean')
return format_python_formatted_type(val, colormap, 'boolean', alignment=Alignment.LEFT)


formatter_for('boolean')(format_value_boolean)
Expand Down Expand Up @@ -318,7 +318,7 @@ def format_floating_point_type(val, colormap, float_precision, decimal_sep=None,
if decimal_sep:
bval = bval.replace('.', decimal_sep)

return colorme(bval, colormap, 'float')
return colorme(bval, colormap, 'float', alignment=Alignment.RIGHT)


formatter_for('float')(format_floating_point_type)
Expand All @@ -329,7 +329,7 @@ def format_integer_type(val, colormap, thousands_sep=None, **_):
# base-10 only for now; support others?
bval = format_integer_with_thousands_sep(val, thousands_sep) if thousands_sep else str(val)
bval = str(bval)
return colorme(bval, colormap, 'int')
return colorme(bval, colormap, 'int', alignment=Alignment.RIGHT)


def format_integer_with_thousands_sep(val, thousands_sep=','):
Expand Down Expand Up @@ -357,7 +357,7 @@ def format_value_timestamp(val, colormap, date_time_format, quote=False, **_):

if quote:
bval = "'%s'" % bval
return colorme(bval, colormap, 'timestamp')
return colorme(bval, colormap, 'timestamp', alignment=Alignment.LEFT)


formatter_for('timestamp')(format_value_timestamp)
Expand Down Expand Up @@ -399,17 +399,18 @@ def round_microseconds(val):

@formatter_for('Date')
def format_value_date(val, colormap, **_):
return format_python_formatted_type(val, colormap, 'date')
return format_python_formatted_type(val, colormap, 'date', alignment=Alignment.LEFT)


@formatter_for('Time')
def format_value_time(val, colormap, **_):
return format_python_formatted_type(val, colormap, 'time')
return format_python_formatted_type(val, colormap, 'time', alignment=Alignment.LEFT)


@formatter_for('Duration')
def format_value_duration(val, colormap, **_):
return format_python_formatted_type(duration_as_str(val.months, val.days, val.nanoseconds), colormap, 'duration')
return format_python_formatted_type(duration_as_str(val.months, val.days, val.nanoseconds),
colormap,'duration', alignment=Alignment.RIGHT)


def duration_as_str(months, days, nanoseconds):
Expand Down Expand Up @@ -485,7 +486,9 @@ def format_value_text(val, encoding, colormap, quote=False, **_):
bval = escapedval
if quote:
bval = "'{}'".format(bval)
return bval if colormap is NO_COLOR_MAP else color_text(bval, colormap, wcwidth.wcswidth(bval))
if colormap is NO_COLOR_MAP:
return bval
return color_text(bval, colormap, wcwidth.wcswidth(bval), alignment=Alignment.LEFT)


# name alias
Expand All @@ -510,7 +513,7 @@ def format_simple_collection(val, cqltype, lbracket, rbracket, encoding,
for s in (lbracket, ', ', rbracket)]
coloredval = lb + sep.join(sval.coloredval for sval in subs) + rb
displaywidth = 2 * len(subs) + sum(sval.displaywidth for sval in subs)
return FormattedValue(bval, coloredval, displaywidth)
return FormattedValue(bval, coloredval, displaywidth, alignment=Alignment.RIGHT)


@formatter_for('list')
Expand Down Expand Up @@ -562,7 +565,7 @@ def subformat(v, t):
+ comma.join(k.coloredval + colon + v.coloredval for (k, v) in subs) \
+ rb
displaywidth = 4 * len(subs) + sum(k.displaywidth + v.displaywidth for (k, v) in subs)
return FormattedValue(bval, coloredval, displaywidth)
return FormattedValue(bval, coloredval, displaywidth, alignment=Alignment.RIGHT)


formatter_for('OrderedDict')(format_value_map)
Expand All @@ -575,7 +578,7 @@ def format_value_utype(val, cqltype, encoding, colormap, date_time_format, float
decimal_sep, thousands_sep, boolean_styles, **_):
def format_field_value(v, t):
if v is None:
return colorme(nullval, colormap, 'error')
return colorme(nullval, colormap, 'error', alignment=Alignment.RIGHT)
return format_value(v, cqltype=t, encoding=encoding, colormap=colormap,
date_time_format=date_time_format, float_precision=float_precision,
nullval=nullval, quote=True, decimal_sep=decimal_sep,
Expand All @@ -596,7 +599,7 @@ def format_field_name(name):
+ comma.join(k.coloredval + colon + v.coloredval for (k, v) in subs) \
+ rb
displaywidth = 4 * len(subs) + sum(k.displaywidth + v.displaywidth for (k, v) in subs)
return FormattedValue(bval, coloredval, displaywidth)
return FormattedValue(bval, coloredval, displaywidth, alignment=Alignment.RIGHT)


NANOS_PER_MICRO = 1000
Expand Down