cqlsh: update encoding for collections

Patch by paul cannon reviewed by brandonwilliams for CASSANDRA-4500
This commit is contained in:
Brandon Williams 2012-08-31 07:42:31 -05:00
parent 39fdebfd49
commit bd27527211
7 changed files with 361 additions and 246 deletions

254
bin/cqlsh
View File

@ -39,7 +39,6 @@ from itertools import groupby
from contextlib import contextmanager, closing
from glob import glob
from functools import partial
from collections import defaultdict
import cmd
import sys
@ -49,19 +48,10 @@ import optparse
import ConfigParser
import codecs
import locale
import re
import platform
import warnings
import csv
# cqlsh should run correctly when run out of a Cassandra source tree,
# out of an unpacked Cassandra tarball, and after a proper package install.
cqlshlibdir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'pylib')
if os.path.isdir(cqlshlibdir):
sys.path.insert(0, cqlshlibdir)
from cqlshlib import cqlhandling, cql3handling, pylexotron, wcwidth
try:
import readline
except ImportError:
@ -105,7 +95,20 @@ except ImportError, e:
import cql.decoders
from cql.cursor import _COUNT_DESCRIPTION, _VOID_DESCRIPTION
from cql.cqltypes import (cql_types, cql_typename, lookup_casstype, lookup_cqltype,
CassandraType)
# cqlsh should run correctly when run out of a Cassandra source tree,
# out of an unpacked Cassandra tarball, and after a proper package install.
cqlshlibdir = os.path.join(os.path.dirname(os.path.realpath(__file__)), '..', 'pylib')
if os.path.isdir(cqlshlibdir):
sys.path.insert(0, cqlshlibdir)
from cqlshlib import cqlhandling, cql3handling, pylexotron
from cqlshlib.displaying import (RED, BLUE, ANSI_RESET, COLUMN_NAME_COLORS,
FormattedValue, colorme)
from cqlshlib.formatting import format_by_type
from cqlshlib.util import trim_if_present
CONFIG_FILE = os.path.expanduser(os.path.join('~', '.cqlshrc'))
HISTORY = os.path.expanduser(os.path.join('~', '.cqlsh_history'))
@ -139,16 +142,6 @@ parser.add_option("-3", "--cql3", action="store_const", dest='cqlversion', const
help="Shortcut notation for --cqlversion=3")
RED = '\033[0;1;31m'
GREEN = '\033[0;1;32m'
YELLOW = '\033[0;1;33m'
BLUE = '\033[0;1;34m'
MAGENTA = '\033[0;1;35m'
CYAN = '\033[0;1;36m'
WHITE = '\033[0;1;37m'
DARK_MAGENTA = '\033[0;35m'
ANSI_RESET = '\033[0m'
CQL_ERRORS = (cql.Error,)
try:
from thrift.Thrift import TException
@ -361,159 +354,18 @@ def full_cql_version(ver):
vertuple = tuple(map(int, ver_parts[0].split('.')) + [ver_parts[1]])
return ver, vertuple
def trim_if_present(s, prefix):
if s.startswith(prefix):
return s[len(prefix):]
return s
class FormattedValue:
def __init__(self, strval, coloredval, displaywidth):
self.strval = strval
self.coloredval = coloredval
self.displaywidth = displaywidth
def __len__(self):
return len(self.strval)
def _pad(self, width, fill=' '):
if width > self.displaywidth:
return fill * (width - self.displaywidth)
def format_value(val, typeclass, output_encoding, addcolor=False, time_format=None,
float_precision=None, colormap=None, nullval=None):
if isinstance(val, DecodeError):
if addcolor:
return colorme(val.thebytes, colormap, 'hex')
else:
return ''
def ljust(self, width, fill=' '):
"""
Similar to self.strval.ljust(width), but takes expected terminal
display width into account for special characters, and does not
take color escape codes into account.
"""
return self.strval + self._pad(width, fill)
def rjust(self, width, fill=' '):
"""
Similar to self.strval.rjust(width), but takes expected terminal
display width into account for special characters, and does not
take color escape codes into account.
"""
return self._pad(width, fill) + self.strval
def color_rjust(self, width, fill=' '):
"""
Similar to self.rjust(width), but uses this value's colored
representation, and does not take color escape codes into account
in determining width.
"""
return self._pad(width, fill) + self.coloredval
def color_ljust(self, width, fill=' '):
"""
Similar to self.ljust(width), but uses this value's colored
representation, and does not take color escape codes into account
in determining width.
"""
return self.coloredval + self._pad(width, fill)
unicode_controlchars_re = re.compile(r'[\x00-\x31\x7f-\xa0]')
controlchars_re = re.compile(r'[\x00-\x31\x7f-\xff]')
def _show_control_chars(match):
txt = repr(match.group(0))
if txt.startswith('u'):
txt = txt[2:-1]
else:
txt = txt[1:-1]
return txt
bits_to_turn_red_re = re.compile(r'\\([^uUx]|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{2}|U[0-9a-fA-F]{8})')
def _make_turn_bits_red_f(color1, color2):
def _turn_bits_red(match):
txt = match.group(0)
if txt == '\\\\':
return '\\'
return color1 + txt + color2
return _turn_bits_red
DEFAULT_VALUE_COLORS = dict(
default=YELLOW,
text=YELLOW,
error=RED,
hex=DARK_MAGENTA,
timestamp=GREEN,
int=GREEN,
float=GREEN,
decimal=GREEN,
boolean=GREEN,
uuid=GREEN,
)
COLUMN_NAME_COLORS = defaultdict(lambda: MAGENTA,
error=RED,
hex=DARK_MAGENTA,
)
def unix_time_from_uuid1(u):
return (u.get_time() - 0x01B21DD213814000) / 10000000.0
def format_value(val, casstype, output_encoding, addcolor=False, time_format='',
float_precision=3, colormap=DEFAULT_VALUE_COLORS, nullval='null'):
casstype = trim_if_present(casstype, 'org.apache.cassandra.db.marshal.')
color = colormap['default']
coloredval = None
displaywidth = None
if val is None:
bval = nullval
color = colormap['error']
elif isinstance(val, DecodeError):
casstype = 'BytesType'
bval = repr(val.thebytes)
color = colormap['hex']
elif casstype == 'UTF8Type':
escapedval = val.replace(u'\\', u'\\\\')
escapedval = unicode_controlchars_re.sub(_show_control_chars, escapedval)
bval = escapedval.encode(output_encoding, 'backslashreplace')
displaywidth = wcwidth.wcswidth(bval.decode(output_encoding))
if addcolor:
tbr = _make_turn_bits_red_f(colormap['hex'], colormap['text'])
coloredval = colormap['text'] + bits_to_turn_red_re.sub(tbr, bval) + ANSI_RESET
elif casstype in ('DateType', 'TimeUUIDType'):
if casstype == 'TimeUUIDType':
val = unix_time_from_uuid1(val)
timestamp = time.localtime(val)
bval = time.strftime(time_format, timestamp)
color = colormap['timestamp']
elif casstype in ('LongType', 'Int32Type', 'IntegerType', 'CounterColumnType'):
# base-10 only for now; support others?
bval = str(val)
color = colormap['int']
elif casstype in ('FloatType', 'DoubleType'):
bval = '%.*g' % (float_precision, val)
color = colormap['float']
elif casstype in ('DecimalType', 'UUIDType', 'BooleanType'):
# let python do these for us
bval = str(val)
color = colormap[cqlruleset.cql_typename(casstype)]
elif casstype == 'BytesType':
bval = ''.join('%02x' % ord(c) for c in val)
color = colormap['hex']
else:
# AsciiType is the only other one known right now, but handle others
val = str(val)
escapedval = val.replace('\\', '\\\\')
bval = controlchars_re.sub(_show_control_chars, escapedval)
if addcolor:
tbr = _make_turn_bits_red_f(colormap['hex'], colormap['text'])
coloredval = colormap['text'] + bits_to_turn_red_re.sub(tbr, bval) + ANSI_RESET
if displaywidth is None:
displaywidth = len(bval)
if not addcolor:
coloredval = bval
elif coloredval is None:
coloredval = color + bval + ANSI_RESET
return FormattedValue(bval, coloredval, displaywidth)
return FormattedValue(val.thebytes)
if not issubclass(typeclass, CassandraType):
typeclass = lookup_casstype(typeclass)
return format_by_type(typeclass, val, output_encoding, colormap=colormap,
addcolor=addcolor, nullval=nullval, time_format=time_format,
float_precision=float_precision)
def show_warning_without_quoting_line(message, category, filename, lineno, file=None, line=None):
if file is None:
@ -657,14 +509,14 @@ class Shell(cmd.Cmd):
cf = self.cql_protect_name(cf)
if override.default_name_type:
print 'ASSUME %s NAMES ARE %s;' \
% (cf, cqlruleset.cql_typename(override.default_name_type))
% (cf, cql_typename(override.default_name_type))
if override.default_value_type:
print 'ASSUME %s VALUES ARE %s;' \
% (cf, cqlruleset.cql_typename(override.default_value_type))
% (cf, cql_typename(override.default_value_type))
for colname, vtype in override.value_types.items():
colname = self.cql_protect_name(colname)
print 'ASSUME %s(%s) VALUES ARE %s;' \
% (cf, colname, cqlruleset.cql_typename(vtype))
% (cf, colname, cql_typename(vtype))
print
def get_cluster_versions(self):
@ -1059,15 +911,7 @@ class Shell(cmd.Cmd):
This is necessary to differentiate some things like ascii vs. blob hex.
"""
if getattr(cursor, 'supports_name_info', False):
return cursor.name_info[num][1]
# This is a pretty big hack, but necessary until we can rely on
# python-cql 1.0.10 being around.
row = cursor.result[cursor.rs_idx - 1]
col = row.columns[num]
schema = cursor.decoder.schema
return schema.name_types.get(col.name, schema.default_name_type)
return cursor.name_info[num][1]
def print_count_result(self, cursor):
if not cursor.result:
@ -1077,21 +921,21 @@ class Shell(cmd.Cmd):
self.writeresult(cursor.result[0])
self.writeresult("")
def print_result(self, cursor):
self.decoding_errors = []
# first pass: see if we have a static column set
def has_static_result_set(self, cursor):
if self.cqlver_atleast(3):
return True # all cql3 resultsets are static, don't bother scanning
last_description = None
for row in cursor:
if last_description is not None and cursor.description != last_description:
static = False
break
return False
last_description = cursor.description
else:
static = True
cursor._reset()
return True
if static:
def print_result(self, cursor):
self.decoding_errors = []
if self.has_static_result_set(cursor):
self.print_static_result(cursor)
else:
self.print_dynamic_result(cursor)
@ -1105,10 +949,10 @@ class Shell(cmd.Cmd):
% (len(self.decoding_errors) - 2), color=RED)
def print_static_result(self, cursor):
colnames, coltypes = zip(*cursor.description)[:2]
colnames = [d[0] for d in cursor.description]
colnames_t = [(name, self.get_nametype(cursor, n)) for (n, name) in enumerate(colnames)]
formatted_names = [self.myformat_colname(name, nametype) for (name, nametype) in colnames_t]
formatted_data = [map(self.myformat_value, row, coltypes) for row in cursor]
formatted_data = [map(self.myformat_value, row, cursor.column_types) for row in cursor]
# determine column widths
widths = [n.displaywidth for n in formatted_names]
@ -1128,10 +972,10 @@ class Shell(cmd.Cmd):
def print_dynamic_result(self, cursor):
for row in cursor:
colnames, coltypes = zip(*cursor.description)[:2]
colnames = [d[0] for d in cursor.description]
colnames_t = [(name, self.get_nametype(cursor, n)) for (n, name) in enumerate(colnames)]
colnames = [self.myformat_colname(name, nametype) for (name, nametype) in colnames_t]
colvals = [self.myformat_value(val, casstype) for (val, casstype) in zip(row, coltypes)]
colvals = map(self.myformat_value, row, cursor.column_types)
line = ' | '.join('%s,%s' % (n.coloredval, v.coloredval) for (n, v) in zip(colnames, colvals))
self.writeresult(' ' + line)
@ -1234,12 +1078,12 @@ class Shell(cmd.Cmd):
cfname = self.cql_protect_name(cfdef.name)
out.write("CREATE TABLE %s (\n" % cfname)
alias = self.cql_protect_name(cfdef.key_alias) if cfdef.key_alias else 'KEY'
keytype = cqlruleset.cql_typename(cfdef.key_validation_class)
keytype = cql_typename(cfdef.key_validation_class)
out.write(" %s %s PRIMARY KEY" % (alias, keytype))
indexed_columns = []
for col in cfdef.column_metadata:
colname = self.cql_protect_name(col.name)
out.write(",\n %s %s" % (colname, cqlruleset.cql_typename(col.validation_class)))
out.write(",\n %s %s" % (colname, cql_typename(col.validation_class)))
if col.index_name is not None:
indexed_columns.append(col)
cf_opts = []
@ -1248,7 +1092,7 @@ class Shell(cmd.Cmd):
if optval is None:
continue
if option in ('comparator', 'default_validation'):
optval = cqlruleset.cql_typename(optval)
optval = cql_typename(optval)
else:
if option == 'row_cache_provider':
optval = trim_if_present(optval, 'org.apache.cassandra.cache.')
@ -1612,11 +1456,11 @@ class Shell(cmd.Cmd):
row = self.cursor.fetchone()
if row is None:
break
fmt = lambda v, d: \
format_value(v, d[1], output_encoding=encoding, nullval=nullval,
fmt = lambda v, t: \
format_value(v, t, output_encoding=encoding, nullval=nullval,
time_format=self.display_time_format,
float_precision=self.display_float_precision).strval
writer.writerow(map(fmt, row, self.cursor.description))
writer.writerow(map(fmt, row, self.cursor.column_types))
rows += 1
finally:
if do_close:
@ -1726,7 +1570,7 @@ class Shell(cmd.Cmd):
if cqltype is None:
continue
try:
validator_class = cqlruleset.find_validator_class(cqltype)
validator_class = lookup_cqltype(cqltype).cass_parameterized_type()
except KeyError:
self.printerr('Error: validator type %s not found.' % cqltype)
else:
@ -1868,7 +1712,7 @@ class Shell(cmd.Cmd):
def help_types(self):
print "\n CQL types recognized by this version of cqlsh:\n"
for t in cqlruleset.cql_types:
for t in cql_types:
print ' ' + t
print """
For information on the various recognizable input formats for these

Binary file not shown.

View File

@ -17,6 +17,7 @@
import re
from warnings import warn
from .cqlhandling import CqlParsingRuleSet, Hint
from cql.cqltypes import cql_types, cql_typename
try:
import json
@ -131,7 +132,7 @@ CqlRuleSet = Cql3ParsingRuleSet()
shorthands = ('completer_for', 'explain_completion',
'dequote_value', 'dequote_name',
'escape_value', 'escape_name',
'maybe_escape_name', 'cql_typename')
'maybe_escape_name')
for shorthand in shorthands:
globals()[shorthand] = getattr(CqlRuleSet, shorthand)
@ -250,7 +251,7 @@ def token_word_completer(ctxt, cass):
@completer_for('storageType', 'typename')
def storagetype_completer(ctxt, cass):
return CqlRuleSet.cql_types
return cql_types
@completer_for('keyspaceName', 'ksname')
def ks_name_completer(ctxt, cass):
@ -611,7 +612,7 @@ def create_cf_option_val_completer(ctxt, cass):
if any(this_opt == opt[0] for opt in CqlRuleSet.obsolete_cf_options):
return ["'<obsolete_option>'"]
if this_opt in ('comparator', 'default_validation'):
return CqlRuleSet.cql_types
return cql_types
if this_opt in ('read_repair_chance', 'bloom_filter_fp_chance'):
return [Hint('<float_between_0_and_1>')]
if this_opt == 'replicate_on_write':

View File

@ -20,6 +20,7 @@
import re
import traceback
from . import pylexotron, util
from cql import cqltypes
Hint = pylexotron.Hint
@ -77,27 +78,6 @@ class CqlParsingRuleSet(pylexotron.ParsingRuleSet):
'SizeTieredCompactionStrategy'
)
cql_type_to_apache_class = {
'ascii': 'AsciiType',
'bigint': 'LongType',
'blob': 'BytesType',
'boolean': 'BooleanType',
'counter': 'CounterColumnType',
'decimal': 'DecimalType',
'double': 'DoubleType',
'float': 'FloatType',
'int': 'Int32Type',
'text': 'UTF8Type',
'timestamp': 'DateType',
'uuid': 'UUIDType',
'varchar': 'UTF8Type',
'varint': 'IntegerType'
}
apache_class_to_cql_type = dict((v,k) for (k,v) in cql_type_to_apache_class.items())
cql_types = sorted(cql_type_to_apache_class.keys())
replication_strategies = (
'SimpleStrategy',
'OldNetworkTopologyStrategy',
@ -361,20 +341,6 @@ class CqlParsingRuleSet(pylexotron.ParsingRuleSet):
print "** New total completion: %r. Checking for further matches...\n" % (first,)
return self.cql_complete_multiple(text, first, init_bindings, startsymbol=startsymbol)
@classmethod
def cql_typename(cls, classname):
fq_classname = 'org.apache.cassandra.db.marshal.'
if classname.startswith(fq_classname):
classname = classname[len(fq_classname):]
try:
return cls.apache_class_to_cql_type[classname]
except KeyError:
return cls.escape_value(classname)
@classmethod
def find_validator_class(cls, cqlname):
return cls.cql_type_to_apache_class[cqlname]
@classmethod
def is_valid_cql_word(cls, s):
return cls.valid_cql_word_re.match(s) is not None and s.lower() not in cls.keywords
@ -410,7 +376,7 @@ class CqlParsingRuleSet(pylexotron.ParsingRuleSet):
@classmethod
def is_counter_col(cls, cfdef, colname):
col_info = [cm for cm in cfdef.column_metadata if cm.name == colname]
return bool(col_info and cls.cql_typename(col_info[0].validation_class) == 'counter')
return bool(col_info and cqltypes.is_counter_type(col_info[0].validation_class))
@staticmethod
def cql2_dequote_value(cqlword):
@ -543,7 +509,7 @@ def cl_completer(ctxt, cass):
@completer_for('storageType', 'typename')
def storagetype_completer(ctxt, cass):
return CqlRuleSet.cql_types
return cqltypes.cql_types
@completer_for('keyspaceName', 'ksname')
def ks_name_completer(ctxt, cass):
@ -850,7 +816,7 @@ def create_cf_option_val_completer(ctxt, cass):
if any(this_opt == opt[0] for opt in CqlRuleSet.obsolete_cf_options):
return ["'<obsolete_option>'"]
if this_opt in ('comparator', 'default_validation'):
return CqlRuleSet.cql_types
return cqltypes.cql_types
if this_opt == 'read_repair_chance':
return [Hint('<float_between_0_and_1>')]
if this_opt == 'replicate_on_write':

View File

@ -0,0 +1,107 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
from collections import defaultdict
RED = '\033[0;1;31m'
GREEN = '\033[0;1;32m'
YELLOW = '\033[0;1;33m'
BLUE = '\033[0;1;34m'
MAGENTA = '\033[0;1;35m'
CYAN = '\033[0;1;36m'
WHITE = '\033[0;1;37m'
DARK_MAGENTA = '\033[0;35m'
ANSI_RESET = '\033[0m'
def colorme(bval, colormap, colorkey):
if colormap is None:
colormap = DEFAULT_VALUE_COLORS
return FormattedValue(bval, colormap[colorkey] + bval + colormap['reset'])
class FormattedValue:
def __init__(self, strval, coloredval=None, displaywidth=None):
self.strval = strval
if coloredval is None:
coloredval = strval
self.coloredval = coloredval
if displaywidth is None:
displaywidth = len(strval)
# displaywidth is useful for display of special unicode characters
# with
self.displaywidth = displaywidth
def __len__(self):
return len(self.strval)
def _pad(self, width, fill=' '):
if width > self.displaywidth:
return fill * (width - self.displaywidth)
else:
return ''
def ljust(self, width, fill=' '):
"""
Similar to self.strval.ljust(width), but takes expected terminal
display width into account for special characters, and does not
take color escape codes into account.
"""
return self.strval + self._pad(width, fill)
def rjust(self, width, fill=' '):
"""
Similar to self.strval.rjust(width), but takes expected terminal
display width into account for special characters, and does not
take color escape codes into account.
"""
return self._pad(width, fill) + self.strval
def color_rjust(self, width, fill=' '):
"""
Similar to self.rjust(width), but uses this value's colored
representation, and does not take color escape codes into account
in determining width.
"""
return self._pad(width, fill) + self.coloredval
def color_ljust(self, width, fill=' '):
"""
Similar to self.ljust(width), but uses this value's colored
representation, and does not take color escape codes into account
in determining width.
"""
return self.coloredval + self._pad(width, fill)
DEFAULT_VALUE_COLORS = dict(
default=YELLOW,
text=YELLOW,
error=RED,
hex=DARK_MAGENTA,
timestamp=GREEN,
int=GREEN,
float=GREEN,
decimal=GREEN,
boolean=GREEN,
uuid=GREEN,
collection=BLUE,
reset=ANSI_RESET,
)
COLUMN_NAME_COLORS = defaultdict(lambda: MAGENTA,
error=RED,
hex=DARK_MAGENTA,
reset=ANSI_RESET,
)

View File

@ -0,0 +1,192 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import re
import time
from collections import defaultdict
from . import wcwidth
from .displaying import colorme, FormattedValue, DEFAULT_VALUE_COLORS
from cql import cqltypes
unicode_controlchars_re = re.compile(r'[\x00-\x31\x7f-\xa0]')
controlchars_re = re.compile(r'[\x00-\x31\x7f-\xff]')
def _show_control_chars(match):
txt = repr(match.group(0))
if txt.startswith('u'):
txt = txt[2:-1]
else:
txt = txt[1:-1]
return txt
bits_to_turn_red_re = re.compile(r'\\([^uUx]|u[0-9a-fA-F]{4}|x[0-9a-fA-F]{2}|U[0-9a-fA-F]{8})')
def _make_turn_bits_red_f(color1, color2):
def _turn_bits_red(match):
txt = match.group(0)
if txt == '\\\\':
return '\\'
return color1 + txt + color2
return _turn_bits_red
default_null_placeholder = 'null'
default_time_format = ''
default_float_precision = 3
default_colormap = DEFAULT_VALUE_COLORS
empty_colormap = defaultdict(lambda: '')
def format_by_type(cqltype, val, encoding, colormap=None, addcolor=False,
nullval=None, time_format=None, float_precision=None):
if nullval is None:
nullval = default_null_placeholder
if val is None:
return colorme(nullval, colormap, 'error')
if addcolor is False:
colormap = empty_colormap
elif colormap is None:
colormap = default_colormap
if time_format is None:
time_format = default_time_format
if float_precision is None:
float_precision = default_float_precision
return format_value(cqltype, val, encoding=encoding, colormap=colormap,
time_format=time_format, float_precision=float_precision,
nullval=nullval)
def format_value_default(val, colormap, **_):
val = str(val)
escapedval = val.replace('\\', '\\\\')
bval = controlchars_re.sub(_show_control_chars, escapedval)
tbr = _make_turn_bits_red_f(colormap['hex'], colormap['text'])
coloredval = colormap['text'] + bits_to_turn_red_re.sub(tbr, bval) + colormap['reset']
return FormattedValue(bval, coloredval)
# Mapping cql type base names ("int", "map", etc) to formatter functions,
# making format_value a generic function
_formatters = {}
def format_value(cqltype, val, **kwargs):
formatter = _formatters.get(cqltype.typename, format_value_default)
return formatter(val, subtypes=cqltype.subtypes, **kwargs)
def formatter_for(typname):
def registrator(f):
_formatters[typname] = f
return f
return registrator
@formatter_for('bytes')
def format_value_bytes(val, colormap, **_):
bval = ''.join('%02x' % ord(c) for c in val)
return colorme(bval, colormap, 'hex')
def format_python_formatted_type(val, colormap, color):
bval = str(val)
return colorme(bval, colormap, color)
@formatter_for('decimal')
def format_value_decimal(val, colormap, **_):
return format_python_formatted_type(val, colormap, 'decimal')
@formatter_for('uuid')
def format_value_uuid(val, colormap, **_):
return format_python_formatted_type(val, colormap, 'uuid')
@formatter_for('boolean')
def format_value_boolean(val, colormap, **_):
return format_python_formatted_type(val, colormap, 'boolean')
def format_floating_point_type(val, colormap, float_precision, **_):
bval = '%.*g' % (float_precision, val)
return colorme(bval, colormap, 'float')
formatter_for('float')(format_floating_point_type)
formatter_for('double')(format_floating_point_type)
def format_integer_type(val, colormap, **_):
# base-10 only for now; support others?
bval = str(val)
return colorme(bval, colormap, 'int')
formatter_for('bigint')(format_integer_type)
formatter_for('int')(format_integer_type)
formatter_for('varint')(format_integer_type)
formatter_for('counter')(format_integer_type)
@formatter_for('timestamp')
def format_value_timestamp(val, colormap, time_format, **_):
bval = time.strftime(time_format, time.localtime(val))
return colorme(bval, colormap, 'timestamp')
@formatter_for('timeuuid')
def format_value_timeuuid(val, colormap, time_format, **_):
utime = unix_time_from_uuid1(val)
bval = time.strftime(time_format, time.localtime(utime))
return colorme(bval, colormap, 'timestamp')
@formatter_for('text')
def format_value_text(val, encoding, colormap, **_):
escapedval = val.replace(u'\\', u'\\\\')
escapedval = unicode_controlchars_re.sub(_show_control_chars, escapedval)
bval = escapedval.encode(encoding, 'backslashreplace')
displaywidth = wcwidth.wcswidth(bval.decode(encoding))
tbr = _make_turn_bits_red_f(colormap['hex'], colormap['text'])
coloredval = colormap['text'] + bits_to_turn_red_re.sub(tbr, bval) + colormap['reset']
return FormattedValue(bval, coloredval)
# name alias
formatter_for('varchar')(format_value_text)
def format_simple_collection(subtype, val, lbracket, rbracket, encoding,
colormap, time_format, float_precision, nullval):
subs = [format_value(subtype, sval, encoding=encoding, colormap=colormap,
time_format=time_format, float_precision=float_precision,
nullval=nullval)
for sval in val]
bval = lbracket + ', '.join(sval.strval for sval in subs) + rbracket
lb, sep, rb = [colormap['collection'] + s + colormap['reset']
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)
@formatter_for('list')
def format_value_list(val, encoding, colormap, time_format, float_precision, subtypes, nullval, **_):
return format_simple_collection(subtypes[0], val, '[', ']', encoding, colormap,
time_format, float_precision, nullval)
@formatter_for('set')
def format_value_set(val, encoding, colormap, time_format, float_precision, subtypes, nullval, **_):
return format_simple_collection(subtypes[0], val, '{', '}', encoding, colormap,
time_format, float_precision, nullval)
@formatter_for('map')
def format_value_map(val, encoding, colormap, time_format, float_precision, subtypes, nullval, **_):
def subformat(v, subtype):
return format_value(subtype, v, encoding=encoding, colormap=colormap,
time_format=time_format, float_precision=float_precision,
nullval=nullval)
subkeytype, subvaltype = subtypes
subs = [(subformat(k, subkeytype), subformat(v, subvaltype)) for (k, v) in val.items()]
bval = '{' + ', '.join(k.strval + ': ' + v.strval for (k, v) in subs) + '}'
lb, comma, colon, rb = [colormap['collection'] + s + colormap['reset']
for s in ('{', ', ', ': ', '}')]
coloredval = lb \
+ 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)

View File

@ -72,3 +72,8 @@ def list_bifilter(pred, iterable):
def identity(x):
return x
def trim_if_present(s, prefix):
if s.startswith(prefix):
return s[len(prefix):]
return s