cqlsh: check for cql driver colname type support, overhaul column coloring Patch by paul cannon, reviewed by brandonwilliams for CASSANDRA-4003
Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/270d0165 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/270d0165 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/270d0165 Branch: refs/heads/cassandra-1.1.0 Commit: 270d0165daab39734ac35142b2e61ea7fa194bc2 Parents: 9fc1d5c Author: Brandon Williams <brandonwilli...@apache.org> Authored: Tue Mar 27 14:29:10 2012 -0500 Committer: Brandon Williams <brandonwilli...@apache.org> Committed: Tue Mar 27 14:29:10 2012 -0500 ---------------------------------------------------------------------- bin/cqlsh | 107 +++++++++++++++++++++++++++++++++++++++----------------- 1 files changed, 75 insertions(+), 32 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/270d0165/bin/cqlsh ---------------------------------------------------------------------- diff --git a/bin/cqlsh b/bin/cqlsh index 8a9d98c..7431bee 100755 --- a/bin/cqlsh +++ b/bin/cqlsh @@ -103,6 +103,7 @@ 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,) @@ -298,55 +299,83 @@ def _show_control_chars(match): 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 _turn_bits_red(match): - txt = match.group(0) - if txt == '\\\\': - return '\\' - return RED + txt + YELLOW - -def format_value(val, casstype, output_encoding, addcolor=False, time_format='', float_precision=3): - color = YELLOW +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): + color = colormap['default'] coloredval = None displaywidth = None if val is None: bval = 'null' - color = RED + color = colormap['error'] elif isinstance(val, DecodeError): casstype = 'BytesType' bval = repr(val.thebytes) - color = RED + 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: - coloredval = YELLOW + bits_to_turn_red_re.sub(_turn_bits_red, bval) + ANSI_RESET - elif casstype == 'DateType': + 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 = GREEN - elif casstype in ('LongType', 'Int32Type', 'IntegerType'): + color = colormap['timestamp'] + elif casstype in ('LongType', 'Int32Type', 'IntegerType', 'CounterColumnType'): # base-10 only for now; support others? bval = str(val) - color = GREEN + color = colormap['int'] elif casstype in ('FloatType', 'DoubleType'): bval = '%.*g' % (float_precision, val) - color = GREEN + color = colormap['float'] elif casstype in ('DecimalType', 'UUIDType', 'BooleanType'): # let python do these for us bval = str(val) - color = GREEN + color = colormap[cql_typename(casstype)] elif casstype == 'BytesType': bval = ''.join('%02x' % ord(c) for c in val) - color = RED + color = colormap['hex'] else: # AsciiType is the only other one known right now, but handle others escapedval = val.replace('\\', '\\\\') bval = controlchars_re.sub(_show_control_chars, escapedval) if addcolor: - coloredval = YELLOW + bits_to_turn_red_re.sub(_turn_bits_red, bval) + ANSI_RESET + 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) @@ -394,22 +423,15 @@ class Shell(cmd.Cmd): else: self.prompt = "" - def myformat_value(self, val, casstype): + def myformat_value(self, val, casstype, **kwargs): if isinstance(val, DecodeError): self.decoding_errors.append(val) return format_value(val, casstype, self.output_codec.name, addcolor=self.color, time_format=self.display_time_format, - float_precision=self.display_float_precision) + float_precision=self.display_float_precision, **kwargs) - def myformat_colname(self, name): - if isinstance(name, DecodeError): - self.decoding_errors.append(name) - name = str(name) - color = RED - else: - color = MAGENTA - return FormattedValue(name, self.applycolor(name, color), - wcwidth.wcswidth(name.decode(self.output_codec.name))) + def myformat_colname(self, name, nametype): + return self.myformat_value(name, nametype, colormap=COLUMN_NAME_COLORS) def report_connection(self): self.show_host() @@ -701,6 +723,25 @@ class Shell(cmd.Cmd): decoder = partial(decoder, overrides=overrides) return decoder + def get_nametype(self, cursor, num): + """ + Determine the Cassandra type of a column name from the current row of + query results on the given cursor. The column in question is given by + its zero-based ordinal number within the row. + + 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) + def print_count_result(self, cursor): if not cursor.result: return @@ -738,7 +779,8 @@ class Shell(cmd.Cmd): def print_static_result(self, cursor): colnames, coltypes = zip(*cursor.description)[:2] - formatted_names = map(self.myformat_colname, colnames) + 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] # determine column widths @@ -760,7 +802,8 @@ class Shell(cmd.Cmd): def print_dynamic_result(self, cursor): for row in cursor: colnames, coltypes = zip(*cursor.description)[:2] - colnames = [self.myformat_colname(name) for name in colnames] + 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)] line = ' | '.join('%s,%s' % (n.coloredval, v.coloredval) for (n, v) in zip(colnames, colvals)) print ' ' + line