Updated Branches: refs/heads/trunk eec230926 -> d5979b301
Add read from/write to support for cqlsh. Patch by Paul Cannon, reviewed by brandonwilliams for CASSANDRA-3479 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/d5979b30 Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/d5979b30 Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/d5979b30 Branch: refs/heads/trunk Commit: d5979b30171385f6ac780b68ea6271413f07a469 Parents: eec2309 Author: paul cannon <[email protected]> Authored: Wed Jan 25 21:46:48 2012 -0600 Committer: Brandon Williams <[email protected]> Committed: Wed Jan 25 21:46:48 2012 -0600 ---------------------------------------------------------------------- bin/cqlsh | 504 +++++++++++++++++++++++++----------- pylib/cqlshlib/cqlhandling.py | 7 +- pylib/cqlshlib/pylexotron.py | 4 +- 3 files changed, 354 insertions(+), 161 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/d5979b30/bin/cqlsh ---------------------------------------------------------------------- diff --git a/bin/cqlsh b/bin/cqlsh index fd664ec..5d68369 100755 --- a/bin/cqlsh +++ b/bin/cqlsh @@ -29,13 +29,15 @@ echo "No appropriate python interpreter found." >&2 exit 1 ":""" +from __future__ import with_statement + description = "CQL Shell for Apache Cassandra" version = "2.0.0" from collections import defaultdict from StringIO import StringIO from itertools import groupby -from functools import partial +from contextlib import contextmanager import cmd import sys @@ -92,6 +94,8 @@ parser.add_option("-C", "--color", action="store_true", help="Enable color output.") parser.add_option("-u", "--username", help="Authenticate as user.") parser.add_option("-p", "--password", help="Authenticate using password.") +parser.add_option("-f", "--file", + help="Execute commands from FILE, then exit") parser.add_option('--debug', action='store_true', help='Show additional debugging information') @@ -123,7 +127,8 @@ cqlhandling.commands_end_with_newline.update(( 'desc', 'show', 'assume', - 'eof', + 'source', + 'capture', 'exit', 'quit' )) @@ -135,6 +140,8 @@ cqlhandling.CqlRuleSet.append_rules(r''' <specialCommand> ::= <describeCommand> | <showCommand> | <assumeCommand> + | <sourceCommand> + | <captureCommand> | <helpCommand> | <exitCommand> ; @@ -158,20 +165,21 @@ cqlhandling.CqlRuleSet.append_rules(r''' | "(" colname=<name> ")" "VALUES" "ARE" colvalues=<storageType> ; -<helpCommand> ::= "HELP" [topic]=( <identifier> | <stringLiteral> )* - | "?" +<sourceCommand> ::= "SOURCE" fname=<stringLiteral> + ; + +<captureCommand> ::= "CAPTURE" ( fname=( <stringLiteral> | "OFF" ) )? + ; + +<helpCommand> ::= ( "HELP" | "?" ) [topic]=( <identifier> | <stringLiteral> )* ; -<exitCommand> ::= ( eof=@"EOF" | "exit" | "quit" ) +<exitCommand> ::= "exit" | "quit" ; <qmark> ::= "?" ; ''') [email protected]_add_completer('exitCommand', 'eof') -def hide_eof_from_completion(ctxt, cqlsh): - return () - @cqlhandling.cql_add_completer('helpCommand', 'topic') def complete_help(ctxt, cqlsh): helpfuncs = [n[5:].upper() for n in cqlsh.get_names() if n.startswith('help_')] @@ -208,6 +216,28 @@ def complete_assume_col(ctxt, cqlsh): cols.append(cfdef.key_alias or 'KEY') return map(maybe_cql_escape, cols) +def complete_source_quoted_filename(ctxt, cqlsh): + partial = ctxt.get_binding('partial', '') + head, tail = os.path.split(partial) + exhead = os.path.expanduser(head) + try: + contents = os.listdir(exhead or '.') + except OSError: + return () + matches = filter(lambda f: f.startswith(tail), contents) + annotated = [] + for f in matches: + match = os.path.join(head, f) + if os.path.isdir(os.path.join(exhead, f)): + match += '/' + annotated.append(match) + return annotated + +cqlhandling.cql_add_completer('sourceCommand', 'fname') \ + (complete_source_quoted_filename) +cqlhandling.cql_add_completer('captureCommand', 'fname') \ + (complete_source_quoted_filename) + class NoKeyspaceError(Exception): pass @@ -322,34 +352,48 @@ class Shell(cmd.Cmd): display_time_format = '%Y-%m-%d %H:%M:%S%z' display_float_precision = 3 num_retries = 4 + show_line_nums = False debug = False + stop = False + shunted_query_out = None def __init__(self, hostname, port, color=False, username=None, - password=None, encoding=None, completekey='tab'): + password=None, encoding=None, stdin=None, tty=True, + completekey='tab', use_conn=None): cmd.Cmd.__init__(self, completekey=completekey) self.hostname = hostname self.port = port - self.conn = cql.connect(hostname, port, user=username, password=password) + if use_conn is not None: + self.conn = use_conn + else: + self.conn = cql.connect(hostname, port, user=username, password=password) self.cursor = self.conn.cursor() self.current_keyspace = None self.color = color if encoding is None: - encoding = sys.stdout.encoding + encoding = sys.stdout.encoding or 'ascii' self.encoding = encoding self.output_codec = codecs.lookup(encoding) self.statement = StringIO() + self.lineno = 1 self.in_comment = False self.schema_overrides = {} - if sys.stdin.isatty(): - self.prompt = Shell.default_prompt + self.prompt = '' + if stdin is None: + stdin = sys.stdin + self.tty = tty + if tty: + self.prompt = self.default_prompt self.report_connection() - self.printout('Use HELP for help.') + print 'Use HELP for help.' else: - self.prompt = "" + self.show_line_nums = True + self.stdin = stdin + self.query_out = sys.stdout def myformat_value(self, val, casstype): return format_value(val, casstype, self.output_codec.name, @@ -361,40 +405,41 @@ class Shell(cmd.Cmd): self.show_version() def show_host(self): - self.printout("Connected to ", newline=False) - self.printout(self.get_cluster_name(), color=BLUE, newline=False) - self.printout(" at %s:%d." % (self.hostname, self.port)) + print "Connected to %s at %s:%d." % \ + (self.applycolor(self.get_cluster_name(), BLUE), + self.hostname, + self.port) def show_version(self): vers = self.get_cluster_versions() vers['shver'] = version - self.printout("[cqlsh %(shver)s | Cassandra %(build)s | CQL spec %(cql)s | Thrift protocol %(thrift)s]" % vers) + print "[cqlsh %(shver)s | Cassandra %(build)s | CQL spec %(cql)s | Thrift protocol %(thrift)s]" % vers def show_assumptions(self): all_overrides = self.schema_overrides.items() all_overrides.sort() if all_overrides: - self.printout('') + print else: - self.printout('No overrides.') + print 'No overrides.' return for keyspace, ksoverrides in groupby(all_overrides, key=lambda x:x[0][0]): keyspace = maybe_cql_escape(keyspace) - self.printout('USE %s;' % keyspace) - self.printout('') + print 'USE %s;' % keyspace + print for (ks, cf), override in ksoverrides: cf = maybe_cql_escape(cf) if override.default_name_type: - self.printout('ASSUME %s NAMES ARE %s;' - % (cf, cql_typename(override.default_name_type))) + print 'ASSUME %s NAMES ARE %s;' \ + % (cf, cql_typename(override.default_name_type)) if override.default_value_type: - self.printout('ASSUME %s VALUES ARE %s;' - % (cf, cql_typename(override.default_value_type))) + print 'ASSUME %s VALUES ARE %s;' \ + % (cf, cql_typename(override.default_value_type)) for colname, vtype in override.value_types.items(): colname = maybe_cql_escape(colname) - self.printout('ASSUME %s(%s) VALUES ARE %s;' - % (cf, colname, cql_typename(vtype))) - self.printout('') + print 'ASSUME %s(%s) VALUES ARE %s;' \ + % (cf, colname, cql_typename(vtype)) + print def get_cluster_versions(self): try: @@ -483,66 +528,124 @@ class Shell(cmd.Cmd): # ===== end thrift-dependent parts ===== def reset_statement(self): + self.reset_prompt() + self.statement.truncate(0) + + def reset_prompt(self): if self.current_keyspace is None: - self.set_prompt(Shell.default_prompt) + self.set_prompt(self.default_prompt) else: - self.set_prompt(Shell.keyspace_prompt % self.current_keyspace) - self.statement.truncate(0) + self.set_prompt(self.keyspace_prompt % self.current_keyspace) - def continue_statement(self): + def set_continue_prompt(self): if self.current_keyspace is None: - self.set_prompt(Shell.continue_prompt) + self.set_prompt(self.continue_prompt) else: spaces = ' ' * len(str(self.current_keyspace)) - self.set_prompt(Shell.keyspace_continue_prompt % spaces) + self.set_prompt(self.keyspace_continue_prompt % spaces) - def precmd(self, line): - self.statement.write(line + '\n') - return self.statement.getvalue() + @contextmanager + def prepare_loop(self): + readline = None + if self.tty and self.completekey: + try: + import readline + except ImportError: + pass + else: + old_completer = readline.get_completer() + readline.set_completer(self.complete) + readline.parse_and_bind(self.completekey+": complete") + try: + yield + finally: + if readline is not None: + readline.set_completer(old_completer) + + def get_input_line(self, prompt=''): + if self.tty: + line = raw_input(self.prompt) + '\n' + else: + sys.stdout.write(self.prompt) + sys.stdout.flush() + line = self.stdin.readline() + if not len(line): + raise EOFError + self.lineno += 1 + return line + + def cmdloop(self): + """ + Adapted from cmd.Cmd's version, because there is literally no way with + cmd.Cmd.cmdloop() to tell the difference between "EOF" showing up in + input and an actual EOF. + """ + with self.prepare_loop(): + while not self.stop: + try: + line = self.get_input_line(self.prompt) + self.statement.write(line) + if self.onecmd(self.statement.getvalue()): + self.reset_statement() + except EOFError: + self.handle_eof() + except cql.Error, cqlerr: + self.printerr(str(cqlerr)) + except KeyboardInterrupt: + self.reset_statement() + print + + def onecmd(self, statement): + """ + Returns true if the statement is complete and was handled (meaning it + can be reset). + """ - def onecmd(self, line): try: - statements, in_batch = cqlhandling.cql_split_statements(line) + statements, in_batch = cqlhandling.cql_split_statements(statement) except pylexotron.LexingError, e: - self.printerr('Invalid syntax at line %d, char %d' % (e.linenum, e.charnum)) - line = line.split('\n')[e.linenum - 1] - self.printerr(' %s' % line) + if self.show_line_nums: + self.printerr('Invalid syntax at char %d' % (e.charnum,)) + else: + self.printerr('Invalid syntax at line %d, char %d' + % (e.linenum, e.charnum)) + statement = statement.split('\n')[e.linenum - 1] + self.printerr(' %s' % statement) self.printerr(' %s^' % (' ' * e.charnum)) - self.reset_statement() - return + return True while statements and not statements[-1]: statements = statements[:-1] if not statements: - self.reset_statement() - return + return True if in_batch or statements[-1][-1][0] != 'endtoken': - self.continue_statement() + self.set_continue_prompt() return - try: - for st in statements: - try: - self.handle_statement(st) - except Exception, e: - if self.debug: - import traceback - traceback.print_exc() - else: - self.printerr(e) - finally: - self.reset_statement() + for st in statements: + try: + self.handle_statement(st) + except Exception, e: + if self.debug: + import traceback + traceback.print_exc() + else: + self.printerr(e) + return True + + def handle_eof(self): + if self.tty: + print + statement = self.statement.getvalue() + if statement.strip(): + if not self.onecmd(statement + ';'): + self.printerr('Incomplete statement at end of file') + self.do_exit() def handle_statement(self, tokens): cmdword = tokens[0][1] - if cmdword != 'EOF': - # and why yes, it /is/ brain-molestingly stupid that cmd uses - # the string "EOF" as a sentinel, so that there's no clear way - # to tell the difference between someone typing "EOF" and a - # real EOF. - cmdword = cmdword.lower() if cmdword == '?': cmdword = 'help' - custom_handler = getattr(self, 'do_' + cmdword, None) + custom_handler = getattr(self, 'do_' + cmdword.lower(), None) if custom_handler: parsed = cqlhandling.cql_whole_parse_tokens(tokens, startsymbol='cqlshCommand') if parsed and not parsed.remainder: @@ -649,10 +752,10 @@ class Shell(cmd.Cmd): def print_count_result(self): if not self.cursor.result: return - self.printout('count') - self.printout('-----') - self.printout(self.cursor.result[0]) - self.printout("") + self.writeresult('count') + self.writeresult('-----') + self.writeresult(self.cursor.result[0]) + self.writeresult("") def print_result(self): # first pass: see if we have a static column set @@ -670,7 +773,7 @@ class Shell(cmd.Cmd): self.print_static_result() else: self.print_dynamic_result() - self.printout("") + self.writeresult("") def print_static_result(self): colnames, coltypes = zip(*self.cursor.description)[:2] @@ -684,13 +787,13 @@ class Shell(cmd.Cmd): # print header header = ' | '.join(self.applycolor(name.ljust(w), MAGENTA) for (name, w) in zip(colnames, widths)) - print ' ' + header.rstrip() - print '-%s-' % '-+-'.join('-' * w for w in widths) + self.writeresult(' ' + header.rstrip()) + self.writeresult('-%s-' % '-+-'.join('-' * w for w in widths)) # print row data for row in formatted_data: line = ' | '.join(col.color_rjust(w) for (col, w) in zip(row, widths)) - print ' ' + line + self.writeresult(' ' + line) def print_dynamic_result(self): for row in self.cursor: @@ -698,7 +801,7 @@ class Shell(cmd.Cmd): colnames = [self.applycolor(name, MAGENTA) for name in colnames] colvals = [self.myformat_value(val, casstype) for (val, casstype) in zip(row, coltypes)] line = ' | '.join(name + ',' + col.coloredval for (col, name) in zip(colvals, colnames)) - print ' ' + line + self.writeresult(' ' + line) def emptyline(self): pass @@ -735,37 +838,34 @@ class Shell(cmd.Cmd): debug=debug_completion, startsymbol='cqlshCommand') def set_prompt(self, prompt): - if sys.stdin.isatty(): + if self.prompt != '': self.prompt = prompt - def print_recreate_keyspace(self, ksdef): + def print_recreate_keyspace(self, ksdef, out): stratclass = trim_if_present(ksdef.strategy_class, 'org.apache.cassandra.locator.') ksname = maybe_cql_escape(ksdef.name) - self.printout("CREATE KEYSPACE %s WITH strategy_class = %s" - % (ksname, cql_escape(stratclass)), - newline=False) + out.write("CREATE KEYSPACE %s WITH strategy_class = %s" + % (ksname, cql_escape(stratclass))) for opname, opval in ksdef.strategy_options.iteritems(): - self.printout("\n AND strategy_options:%s = %s" % (opname, cql_escape(opval)), - newline=False) - self.printout(';') + out.write("\n AND strategy_options:%s = %s" % (opname, cql_escape(opval))) + out.write(';\n') if ksdef.cf_defs: - self.printout('\nUSE %s;' % ksname) + out.write('\nUSE %s;\n' % ksname) for cf in ksdef.cf_defs: - self.printout('') - self.print_recreate_columnfamily(cf) + out.write('\n') + self.print_recreate_columnfamily(cf, out) - def print_recreate_columnfamily(self, cfdef): + def print_recreate_columnfamily(self, cfdef, out): cfname = maybe_cql_escape(cfdef.name) - self.printout("CREATE COLUMNFAMILY %s (" % cfname) + out.write("CREATE COLUMNFAMILY %s (\n" % cfname) alias = cfdef.key_alias if cfdef.key_alias else 'KEY' keytype = cql_typename(cfdef.key_validation_class) - self.printout(" %s %s PRIMARY KEY" % (alias, keytype), newline=False) + out.write(" %s %s PRIMARY KEY" % (alias, keytype)) indexed_columns = [] for col in cfdef.column_metadata: colname = maybe_cql_escape(col.name) - self.printout(",\n %s %s" % (colname, cql_typename(col.validation_class)), - newline=False) + out.write(",\n %s %s" % (colname, cql_typename(col.validation_class))) if col.index_name is not None: indexed_columns.append(col) notable_columns = [] @@ -778,65 +878,64 @@ class Shell(cmd.Cmd): else: optval = cql_escape(optval) notable_columns.append((option, optval)) - self.printout('\n)', newline=False) + out.write('\n)') if notable_columns: joiner = 'WITH' for optname, optval in notable_columns: - self.printout(" %s\n %s=%s" % (joiner, optname, optval), newline=False) + out.write(" %s\n %s=%s" % (joiner, optname, optval)) joiner = 'AND' - self.printout(";") + out.write(";\n") for col in indexed_columns: - self.printout('') + out.write('\n') # guess CQL can't represent index_type or index_options - self.printout('CREATE INDEX %s ON %s (%s);' - % (col.index_name, cfname, maybe_cql_escape(col.name))) + out.write('CREATE INDEX %s ON %s (%s);\n' + % (col.index_name, cfname, maybe_cql_escape(col.name))) def describe_keyspace(self, ksname): - self.printout('') - self.print_recreate_keyspace(self.get_keyspace(ksname)) - self.printout('') + print + self.print_recreate_keyspace(self.get_keyspace(ksname), sys.stdout) + print def describe_columnfamily(self, cfname): - self.printout('') - self.print_recreate_columnfamily(self.get_columnfamily(cfname)) - self.printout('') + print + self.print_recreate_columnfamily(self.get_columnfamily(cfname), sys.stdout) + print def describe_columnfamilies(self, ksname): if ksname is None: for k in self.get_keyspaces(): - self.printout('Keyspace %s' % (k.name,)) - self.printout('---------%s\n' % ('-' * len(k.name))) + print 'Keyspace %s' % (k.name,) + print '---------%s\n' % ('-' * len(k.name)) cmd.Cmd.columnize(self, [c.name for c in k.cf_defs]) - self.printout('') + print else: try: names = self.get_columnfamily_names(ksname) except cql.cassandra.ttypes.NotFoundException: raise KeyspaceNotFound('Keyspace %s not found.' % (ksname,)) - self.printout('') + print cmd.Cmd.columnize(self, names) - self.printout('') + print def describe_cluster(self): - self.printout('Cluster: %s' % self.get_cluster_name()) + print 'Cluster: %s' % self.get_cluster_name() p = trim_if_present(self.get_partitioner(), 'org.apache.cassandra.dht.') - self.printout('Partitioner: %s' % p) + print 'Partitioner: %s' % p snitch = trim_if_present(self.get_snitch(), 'org.apache.cassandra.locator.') - self.printout('Snitch: %s' % snitch) - self.printout('') + print 'Snitch: %s\n' % snitch if self.current_keyspace is not None and self.current_keyspace != 'system': - self.printout("Range ownership:") + print "Range ownership:" ring = self.get_ring() for entry in ring: - self.printout(' %39s [%s]' % (entry.start_token, ', '.join(entry.endpoints))) - self.printout('') + print ' %39s [%s]' % (entry.start_token, ', '.join(entry.endpoints)) + print def describe_schema(self): - self.printout('') + print for k in self.get_keyspaces(): - self.print_recreate_keyspace(k) - self.printout('') + self.print_recreate_keyspace(k, sys.stdout) + print def do_describe(self, parsed): """ @@ -998,33 +1097,116 @@ class Shell(cmd.Cmd): self.add_assumption(params['ks'], params['cf'], params['colname'], overridetype, validator_class) - def do_EOF(self, parsed): + def do_source(self, parsed): + """ + SOURCE [cqlsh only] + + Executes a file containing CQL statements. Gives the output for each + statement in turn, if any, or any errors that occur along the way. + + Errors do NOT abort execution of the CQL source file. + + Usage: + + SOURCE '<file>'; + + That is, the path to the file to be executed must be given inside a + string literal. The path is interpreted relative to the current working + directory. The tilde shorthand notation ("~/mydir") is supported for + referring to $HOME. + + See also the --file option to cqlsh. + """ + + fname = parsed.get_binding('fname') + fname = os.path.expanduser(cql_dequote(fname)) + try: + f = open(fname, 'r') + except IOError, e: + self.printerr('Could not open %r: %s' % (fname, e)) + return + subshell = Shell(self.hostname, self.port, color=self.color, + encoding=self.encoding, stdin=f, tty=False, + use_conn=self.conn) + subshell.cmdloop() + f.close() + + def do_capture(self, parsed): """ - EOF [cqlsh only] + CAPTURE [cqlsh only] + + Begins capturing command output and appending it to a specified file. + Output will not be shown at the console while it is captured. - An end-of-file condition on the input stream causes cqlsh to exit. + Usage: - The command 'EOF' also exits cqlsh, but this is only because of an - annoying feature of Python's cmd.Cmd, and it is not expected to - stay this way. See also 'EXIT', which will continue to work. + CAPTURE '<file>'; + CAPTURE OFF; + CAPTURE; + + That is, the path to the file to be executed must be given inside a + string literal. The path is interpreted relative to the current working + directory. The tilde shorthand notation ("~/mydir") is supported for + referring to $HOME. + + Only query result output is captured. Errors and output from cqlsh-only + commands will still be shown in the cqlsh session. + + To stop capturing output and show it in the cqlsh session again, use + CAPTURE OFF. + + To inspect the current capture configuration, use CAPTURE with no + arguments. """ - if sys.stdin.isatty(): print - self.do_exit(None) + fname = parsed.get_binding('fname') + if fname is None: + if self.shunted_query_out is not None: + print "Currently capturing query output to %r." % (self.query_out.name,) + else: + print "Currently not capturing query output." + return + + if fname.upper() == 'OFF': + if self.shunted_query_out is None: + self.printerr('Not currently capturing output.') + return + self.query_out.close() + self.query_out = self.shunted_query_out + self.color = self.shunted_color + self.shunted_query_out = None + del self.shunted_color + return + + if self.shunted_query_out is not None: + self.printerr('Already capturing output to %s. Use CAPTURE OFF' + ' to disable.' % (self.query_out.name,)) + return - def do_exit(self, parsed): + fname = os.path.expanduser(cql_dequote(fname)) + try: + f = open(fname, 'a') + except IOError, e: + self.printerr('Could not open %r for append: %s' % (fname, e)) + return + self.shunted_query_out = self.query_out + self.shunted_color = self.color + self.query_out = f + self.color = False + print 'Now capturing query output to %r.' % (fname,) + + def do_exit(self, parsed=None): """ EXIT/QUIT [cqlsh only] Exits cqlsh. """ - - sys.exit() + self.stop = True do_quit = do_exit def get_names(self): names = cmd.Cmd.get_names(self) - for hide_from_help in ('do_EOF', 'do_quit'): + for hide_from_help in ('do_quit',): names.remove(hide_from_help) return names @@ -1046,9 +1228,9 @@ class Shell(cmd.Cmd): cmd.Cmd.do_help(self, cql_dequote(t).lower()) def help_types(self): - self.printout("\n CQL types recognized by this version of cqlsh:\n") + print "\n CQL types recognized by this version of cqlsh:\n" for t in cqlhandling.cql_types: - self.printout(' ' + t) + print ' ' + t print """ For information on the various recognizable input formats for these types, or on controlling the formatting of cqlsh query output, see @@ -1670,13 +1852,17 @@ class Shell(cmd.Cmd): return text return color + text + ANSI_RESET - def printout(self, text, color=None, newline=True, out=None): + def writeresult(self, text, color=None, newline=True, out=None): if out is None: - out = sys.stdout + out = self.query_out out.write(self.applycolor(str(text), color) + ('\n' if newline else '')) - def printerr(self, text, color=RED, newline=True): - self.printout(text, color, newline=newline, out=sys.stderr) + def printerr(self, text, color=RED, newline=True, shownum=None): + if shownum is None: + shownum = self.show_line_nums + if shownum: + text = '%s:%d:%s' % (self.stdin.name, self.lineno, text) + self.writeresult(text, color, newline=newline, out=sys.stderr) def add_assumption(self, ksname, cfname, colname, valtype, valclass): try: @@ -1716,7 +1902,7 @@ def option_with_default(cparser_getter, section, option, default=None): return default def should_use_color(): - if not sys.stdin.isatty(): + if not sys.stdout.isatty(): return False if os.environ.get('TERM', 'dumb') == 'dumb': return False @@ -1745,6 +1931,8 @@ def read_options(cmdlineargs, environment): # default yes if tty optvalues.color = should_use_color() optvalues.debug = False + optvalues.file = None + optvalues.tty = sys.stdin.isatty() (options, arguments) = parser.parse_args(cmdlineargs, values=optvalues) @@ -1759,6 +1947,10 @@ def read_options(cmdlineargs, environment): if len(arguments) > 1: port = arguments[1] + if options.file is not None: + options.color = False + options.tty = False + try: port = int(port) except ValueError: @@ -1774,12 +1966,22 @@ def main(options, hostname, port): delims += '.' readline.set_completer_delims(delims) + if options.file is None: + stdin = None + else: + try: + stdin = open(options.file, 'r') + except IOError, e: + sys.exit("Can't open %r: %s" % (options.file, e)) + try: shell = Shell(hostname, port, color=options.color, username=options.username, password=options.password, + stdin=stdin, + tty=options.tty, completekey=options.completekey) except KeyboardInterrupt: sys.exit('Connection aborted.') @@ -1788,24 +1990,10 @@ def main(options, hostname, port): if options.debug: shell.debug = True - while True: - try: - shell.cmdloop() - except SystemExit: - if readline is not None: - readline.write_history_file(HISTORY) - break - except cql.Error, cqlerr: - shell.printerr(str(cqlerr)) - except KeyboardInterrupt: - shell.reset_statement() - print - except Exception, err: - if options.debug: - import traceback - traceback.print_exc() - else: - shell.printerr("Exception: %s" % err) + shell.cmdloop() + + if readline is not None: + readline.write_history_file(HISTORY) if __name__ == '__main__': main(*read_options(sys.argv[1:], os.environ)) http://git-wip-us.apache.org/repos/asf/cassandra/blob/d5979b30/pylib/cqlshlib/cqlhandling.py ---------------------------------------------------------------------- diff --git a/pylib/cqlshlib/cqlhandling.py b/pylib/cqlshlib/cqlhandling.py index 30c22fb..340d471 100644 --- a/pylib/cqlshlib/cqlhandling.py +++ b/pylib/cqlshlib/cqlhandling.py @@ -715,9 +715,10 @@ def cql_complete_single(text, partial, init_bindings={}, ignore_case=True, start partial = prefix + partial if tokens and tokens[-1][0] == 'unclosedComment': return [] + bindings['partial'] = partial # find completions for the position - completions = CqlRuleSet.complete(startsymbol, tokens, init_bindings) + completions = CqlRuleSet.complete(startsymbol, tokens, bindings) hints, strcompletes = list_bifilter(pylexotron.is_hint, completions) @@ -743,6 +744,10 @@ def cql_complete_single(text, partial, init_bindings={}, ignore_case=True, start # fills in the closing quote for us. candidates = [cql_escape(cql_dequote(c))[len(prefix)+1:-1] for c in candidates] + # the above process can result in an empty string; this doesn't help for + # completions + candidates = filter(None, candidates) + # prefix a space when desirable for pleasant cql formatting if tokens: newcandidates = [] http://git-wip-us.apache.org/repos/asf/cassandra/blob/d5979b30/pylib/cqlshlib/pylexotron.py ---------------------------------------------------------------------- diff --git a/pylib/cqlshlib/pylexotron.py b/pylib/cqlshlib/pylexotron.py index fd769a6..ff099ff 100644 --- a/pylib/cqlshlib/pylexotron.py +++ b/pylib/cqlshlib/pylexotron.py @@ -89,8 +89,8 @@ class ParseContext: self.remainder, newname) def __repr__(self): - return '<%s matched=%r remainder=%r prodname=%r>' % (self.__class__.__name__, self.matched, self.remainder, - self.productionname) + return '<%s matched=%r remainder=%r prodname=%r bindings=%r>' \ + % (self.__class__.__name__, self.matched, self.remainder, self.productionname, self.bindings) class matcher: def __init__(self, arg):
