def __init__(self, host, port, username, password, logfile, filename, ssl, read_only, timeout): self.logfile = logfile self.filename = filename self.read_only = read_only self.neo4j = Neo4j(host, port, username, password, ssl, timeout) self.cypher = Cypher()
def __init__(self, labels, relationship_types, properties): super(CypherCompleter, self).__init__() self.labels = labels self.relationship_types = relationship_types self.properties = properties self.cypher = Cypher()
class CypherCompleter(Completer): def __init__(self, labels, relationship_types, properties): super(CypherCompleter, self).__init__() self.labels = labels self.relationship_types = relationship_types self.properties = properties self.cypher = Cypher() def get_completions(self, document, complete_event): text_before_cursor = document.text_before_cursor if currently_inside_quotes(text_before_cursor): return elif typing_label(text_before_cursor): choices = self.labels lookup = everything_after_last(":", text_before_cursor) elif typing_relationship(text_before_cursor): choices = self.relationship_types lookup = everything_after_last(":", text_before_cursor) elif typing_property(text_before_cursor): period_loc = text_before_cursor.rfind(".") variable_start_loc = max( text_before_cursor.rfind("(", 0, period_loc), text_before_cursor.rfind(" ", 0, period_loc)) variable = text_before_cursor[variable_start_loc + 1:period_loc] if variable.isalnum() and any(c.isalpha() for c in variable): choices = self.properties lookup = everything_after_last(".", text_before_cursor) else: return elif text_before_cursor and text_before_cursor[-1].isalpha(): last_cypher_word = self.most_recent_cypher_word(text_before_cursor) choices = self.cypher.most_probable_next_keyword(last_cypher_word) lookup = last_alphabetic_chunk(text_before_cursor) else: return completions = find_matches(lookup, choices) for completion in completions: yield Completion(completion, -len(lookup)) def most_recent_cypher_word(self, chars): text = " " + chars keyword_indices = [(word, text.rfind(" " + word + " ")) for word in self.cypher.KEYWORDS] function_indices = [(word, text.rfind(" " + word + "(")) for word in self.cypher.FUNCTIONS] indices = keyword_indices + function_indices # If no keywords were found, we want to be in the "" state in the Markov model. if not any([i[1] > -1 for i in indices]): return "" most_recent = max(indices, key=lambda i: i[1])[0] return most_recent
from cycli.cypher import Cypher from misc.graphgist import get_all_queries import re cypher = Cypher() queries = get_all_queries() # Each Cypher word is a state in the Markov model. We're also adding a "" state; this means there are no previous words # (we're at the beginning of the query). cypher_words = [""] + cypher.words() # Store the model in a dictionary of dictionaries. markov = {i: {j:0.0 for j in cypher_words} for i in cypher_words} for query in queries: # Find the indices of Cypher functions and keywords separately. This results in a list of tuples for each word and its # index, e.g. [('MATCH', 0), ('WHERE', 13), ('RETURN', 29)]. function_indices = [] for word in cypher.FUNCTIONS: idx = [m.start() for m in re.finditer(" " + word + "\s+\(", query)] for i in idx: function_indices.append((word, i)) # Find the keywords. Make sure they're surrounded by spaces so that we don't grab words within words. keyword_indices = [] for word in cypher.KEYWORDS:
def cypher(): from cycli.cypher import Cypher return Cypher()
class Cycli: def __init__(self, host, port, username, password, logfile, filename, ssl, read_only, timeout): self.logfile = logfile self.filename = filename self.read_only = read_only self.neo4j = Neo4j(host, port, username, password, ssl, timeout) self.cypher = Cypher() def write_to_logfile(self, query, response): headers = response["headers"] rows = response["rows"] duration = response["duration"] error = response["error"] self.logfile.write("> {}\n".format(query)) self.logfile.write("{}\n".format(pretty_table(headers, rows))) if error is False: self.logfile.write("{} ms\n\n".format(duration)) @staticmethod def write_to_csvfile(headers, rows): filename = "cycli {}.csv".format(datetime.now().strftime("%Y-%m-%d at %I.%M.%S %p")) with open(filename, "wb") as csvfile: csvwriter = csv.writer(csvfile, quotechar=str('"'), quoting=csv.QUOTE_NONNUMERIC, delimiter=str(",")) csvwriter.writerow(headers) for row in rows: csvwriter.writerow(row) csvfile.close() def run(self): labels = self.neo4j.get_labels() relationship_types = self.neo4j.get_relationship_types() properties = self.neo4j.get_property_keys() if self.filename: with open(self.filename, "rb") as f: queries = split_queries_on_semicolons(f.read()) for query in queries: print("> " + query) self.handle_query(query) print() return click.secho(" ______ __ __ ______ __ __ ", fg="red") click.secho("/\ ___\ /\ \_\ \ /\ ___\ /\ \ /\ \ ", fg="yellow") click.secho("\ \ \____ \ \____ \ \ \ \____ \ \ \____ \ \ \ ", fg="green") click.secho(" \ \_____\ \/\_____\ \ \_____\ \ \_____\ \ \_\ ", fg="blue") click.secho(" \/_____/ \/_____/ \/_____/ \/_____/ \/_/ ", fg="magenta") print("Cycli version: {}".format(__version__)) print("Neo4j version: {}".format(".".join(map(str, self.neo4j.neo4j_version)))) print("Bug reports: https://github.com/nicolewhite/cycli/issues\n") completer = CypherCompleter(labels, relationship_types, properties) layout = create_prompt_layout( lexer=CypherLexer, get_prompt_tokens=get_tokens, reserve_space_for_menu=8, ) buff = CypherBuffer( accept_action=AcceptAction.RETURN_DOCUMENT, history=FileHistory(filename=os.path.expanduser('~/.cycli_history')), completer=completer, complete_while_typing=True, ) application = Application( style=PygmentsStyle(CypherStyle), buffer=buff, layout=layout, on_exit=AbortAction.RAISE_EXCEPTION, key_bindings_registry=CypherBinder.registry ) cli = CommandLineInterface(application=application, eventloop=create_eventloop()) try: while True: document = cli.run() query = document.text self.handle_query(query) except UserWantsOut: print("Goodbye!") except Exception as e: print(e) def handle_query(self, query): run_n = re.match('run-([0-9]+) (.*)', query, re.DOTALL) save_csv = query.startswith("save-csv ") if self.cypher.is_a_write_query(query) and self.read_only: print("Query aborted. You are in read-only mode.") elif query in ["quit", "exit"]: raise UserWantsOut elif query == "help": print_help() elif query == "refresh": self.neo4j.refresh() elif query == "schema": self.neo4j.print_schema() elif query == "schema-indexes": self.neo4j.print_indexes() elif query == "schema-constraints": self.neo4j.print_constraints() elif query == "schema-labels": self.neo4j.print_labels() elif query == "schema-rels": self.neo4j.print_relationship_types() elif query.startswith("env"): if query == "env": for key, value in self.neo4j.parameters.items(): print("{0}={1}".format(key, value)) else: key = query[3:] key = key.strip("'\"[]") value = self.neo4j.parameters.get(key) if value is not None: print(value) elif query.startswith("export "): if "=" not in query: print("Set parameters with export key=value.") else: params = query.replace("export ", "").strip() key, value = params.split("=", 1) key = key.strip() value = value.strip() try: value = eval(value) self.neo4j.update_parameters(key, value) except Exception as e: print(e) else: count = int(run_n.group(1)) if run_n else 1 query = run_n.group(2) if run_n else query query = query[len("save-csv "):] if save_csv else query if count <= 0 or not query: print("Check your syntax. cycli expects run-{n} {query} where {n} is an integer > 0 and {query} is a Cypher query.") return error = False total_duration = 0 index = 0 while index < count: response = self.neo4j.cypher(query) headers = response["headers"] rows = response["rows"] duration = response["duration"] error = response["error"] profile = response.get("profile") if error is False: print(pretty_table(headers, rows)) ms = "Run {}: {} ms\n".format(index + 1, duration) if run_n else "{} ms".format(duration) print(ms) if profile: self.neo4j.print_profile(profile) if save_csv: self.write_to_csvfile(headers, rows) else: print(error) if self.logfile: self.write_to_logfile(query, response) total_duration += duration index += 1 if run_n and error is False: print("Total duration: {} ms".format(total_duration))
class CypherCompleter(Completer): def __init__(self, labels, relationship_types, properties): super(CypherCompleter, self).__init__() self.labels = labels self.relationship_types = relationship_types self.properties = properties self.cypher = Cypher() def get_completions(self, document, complete_event): text_before_cursor = document.text_before_cursor choices = [] lookup = "" if self.exists_unclosed_char("'", text_before_cursor) or self.exists_unclosed_char('"', text_before_cursor): return elif self.typing_label(text_before_cursor): choices = self.labels lookup = self.everything_after_last(":", text_before_cursor) elif self.typing_relationship(text_before_cursor): choices = self.relationship_types lookup = self.everything_after_last(":", text_before_cursor) elif self.typing_property(text_before_cursor): period_loc = text_before_cursor.rfind(".") variable_start_loc = max(text_before_cursor.rfind("(", 0, period_loc), text_before_cursor.rfind(" ", 0, period_loc)) variable = text_before_cursor[variable_start_loc + 1:period_loc] if variable.isalnum() and any(c.isalpha() for c in variable): choices = self.properties lookup = self.everything_after_last(".", text_before_cursor) else: return elif text_before_cursor: if text_before_cursor[-1].isalpha(): last_cypher_word = self.most_recent_cypher_word(text_before_cursor) choices = self.cypher.most_probable_next_keyword(last_cypher_word) lookup = self.last_alphabetic_chunk(text_before_cursor) else: return completions = self.find_matches(lookup, choices) for completion in completions: yield Completion(completion, -len(lookup)) @staticmethod def find_matches(word, choices): word = word.lower() lower_choices = [x.lower() for x in choices] completions = [] for i, choice in enumerate(lower_choices): if choice.startswith(word): completions.append(choices[i]) return completions def most_recent_cypher_word(self, chars): text = " " + chars keyword_indices = [(word, text.rfind(" " + word + " ")) for word in self.cypher.KEYWORDS] function_indices = [(word, text.rfind(" " + word + "(")) for word in self.cypher.FUNCTIONS] indices = keyword_indices + function_indices # If no keywords were found, we want to be in the "" state in the Markov model. if not any([i[1] > -1 for i in indices]): return "" most_recent = max(indices, key=lambda i:i[1])[0] return most_recent @staticmethod def last_alphabetic_chunk(chars): chars = list(chars) chars.reverse() keep = [] for c in chars: if c.isalpha(): keep.append(c) else: break keep.reverse() return "".join(keep) @staticmethod def everything_after_last(char, chars): chars = chars.replace("`", "") loc = chars.rfind(char) return chars[loc + 1:] @staticmethod def exists_unclosed_char(char, chars): return chars.count(char) % 2 != 0 @staticmethod def exists_unclosed_pattern(open_char, close_char, chars): return chars.count(open_char) != chars.count(close_char) def colon_inside_unclosed_pattern(self, open_char, close_char, chars): return self.exists_unclosed_pattern(open_char, close_char, chars) and chars.rfind(":") > chars.rfind(open_char) def typing_relationship(self, chars): return self.colon_inside_unclosed_pattern("[", "]", chars) and chars.rfind("[") > chars.rfind("(") def typing_label(self, chars): return self.colon_inside_unclosed_pattern("(", ")", chars) and chars.rfind("(") > chars.rfind("[") def typing_property(self, chars): chars = list(chars) chars.reverse() skip = ["_", "`"] # Skip spaces if we're inside backticks. if self.exists_unclosed_char("`", chars): skip.append(" ") for c in chars: if not c.isalnum() and c not in skip: return c == "." return False
class CypherCompleter(Completer): def __init__(self, labels, relationship_types, properties): super(CypherCompleter, self).__init__() self.labels = labels self.relationship_types = relationship_types self.properties = properties self.cypher = Cypher() def get_completions(self, document, complete_event): text_before_cursor = document.text_before_cursor choices = [] lookup = "" if self.exists_unclosed_char( "'", text_before_cursor) or self.exists_unclosed_char( '"', text_before_cursor): return elif self.typing_label(text_before_cursor): choices = self.labels lookup = self.everything_after_last(":", text_before_cursor) elif self.typing_relationship(text_before_cursor): choices = self.relationship_types lookup = self.everything_after_last(":", text_before_cursor) elif self.typing_property(text_before_cursor): period_loc = text_before_cursor.rfind(".") variable_start_loc = max( text_before_cursor.rfind("(", 0, period_loc), text_before_cursor.rfind(" ", 0, period_loc)) variable = text_before_cursor[variable_start_loc + 1:period_loc] if variable.isalnum() and any(c.isalpha() for c in variable): choices = self.properties lookup = self.everything_after_last(".", text_before_cursor) else: return elif text_before_cursor: if text_before_cursor[-1].isalpha(): last_cypher_word = self.most_recent_cypher_word( text_before_cursor) choices = self.cypher.most_probable_next_keyword( last_cypher_word) lookup = self.last_alphabetic_chunk(text_before_cursor) else: return completions = self.find_matches(lookup, choices) for completion in completions: yield Completion(completion, -len(lookup)) @staticmethod def find_matches(word, choices): word = word.lower() lower_choices = [x.lower() for x in choices] completions = [] for i, choice in enumerate(lower_choices): if choice.startswith(word): completions.append(choices[i]) return completions def most_recent_cypher_word(self, chars): text = " " + chars keyword_indices = [(word, text.rfind(" " + word + " ")) for word in self.cypher.KEYWORDS] function_indices = [(word, text.rfind(" " + word + "(")) for word in self.cypher.FUNCTIONS] indices = keyword_indices + function_indices # If no keywords were found, we want to be in the "" state in the Markov model. if not any([i[1] > -1 for i in indices]): return "" most_recent = max(indices, key=lambda i: i[1])[0] return most_recent @staticmethod def last_alphabetic_chunk(chars): chars = list(chars) chars.reverse() keep = [] for c in chars: if c.isalpha(): keep.append(c) else: break keep.reverse() return "".join(keep) @staticmethod def everything_after_last(char, chars): chars = chars.replace("`", "") loc = chars.rfind(char) return chars[loc + 1:] @staticmethod def exists_unclosed_char(char, chars): return chars.count(char) % 2 != 0 @staticmethod def exists_unclosed_pattern(open_char, close_char, chars): return chars.count(open_char) != chars.count(close_char) def colon_inside_unclosed_pattern(self, open_char, close_char, chars): return self.exists_unclosed_pattern( open_char, close_char, chars) and chars.rfind(":") > chars.rfind(open_char) def typing_relationship(self, chars): return self.colon_inside_unclosed_pattern( "[", "]", chars) and chars.rfind("[") > chars.rfind("(") def typing_label(self, chars): return self.colon_inside_unclosed_pattern( "(", ")", chars) and chars.rfind("(") > chars.rfind("[") def typing_property(self, chars): chars = list(chars) chars.reverse() skip = ["_", "`"] # Skip spaces if we're inside backticks. if self.exists_unclosed_char("`", chars): skip.append(" ") for c in chars: if not c.isalnum() and c not in skip: return c == "." return False
from cycli.cypher import Cypher from misc.graphgist import get_all_queries import re cypher = Cypher() queries = get_all_queries() # Each Cypher word is a state in the Markov model. We're also adding a "" state; this means there are no previous words # (we're at the beginning of the query). cypher_words = [""] + cypher.words() # Store the model in a dictionary of dictionaries. markov = {i: {j: 0.0 for j in cypher_words} for i in cypher_words} for query in queries: # Find the indices of Cypher functions and keywords separately. This results in a list of tuples for each word and its # index, e.g. [('MATCH', 0), ('WHERE', 13), ('RETURN', 29)]. function_indices = [] for word in cypher.FUNCTIONS: idx = [ m.start() for m in re.finditer(" " + word + "\s+\(", query, re.IGNORECASE) ] for i in idx: function_indices.append((word, i)) # Find the keywords. Make sure they're surrounded by spaces so that we don't grab words within words.
class Cycli: def __init__(self, host, port, username, password, logfile, filename, ssl, read_only, timeout): self.logfile = logfile self.filename = filename self.read_only = read_only self.neo4j = Neo4j(host, port, username, password, ssl, timeout) self.cypher = Cypher() def write_to_logfile(self, query, response): headers = response["headers"] rows = response["rows"] duration = response["duration"] error = response["error"] self.logfile.write("> {}\n".format(query)) self.logfile.write("{}\n".format(pretty_table(headers, rows))) if error is False: self.logfile.write("{} ms\n\n".format(duration)) @staticmethod def write_to_csvfile(headers, rows): filename = "cycli {}.csv".format(datetime.now().strftime("%Y-%m-%d at %I.%M.%S %p")) with open(filename, "wt") as csvfile: csvwriter = csv.writer(csvfile, quotechar=str('"'), quoting=csv.QUOTE_NONNUMERIC, delimiter=str(",")) csvwriter.writerow(headers) for row in rows: csvwriter.writerow(row) csvfile.close() def run(self): labels = self.neo4j.get_labels() relationship_types = self.neo4j.get_relationship_types() properties = self.neo4j.get_property_keys() if self.filename: queries = self.filename.read() queries = queries.split(";")[:-1] for query in queries: query += ";" query = query.strip() print("> " + query) self.handle_query(query) print() return click.secho(" ______ __ __ ______ __ __ ", fg="red") click.secho("/\ ___\ /\ \_\ \ /\ ___\ /\ \ /\ \ ", fg="yellow") click.secho("\ \ \____ \ \____ \ \ \ \____ \ \ \____ \ \ \ ", fg="green") click.secho(" \ \_____\ \/\_____\ \ \_____\ \ \_____\ \ \_\ ", fg="blue") click.secho(" \/_____/ \/_____/ \/_____/ \/_____/ \/_/ ", fg="magenta") print("Cycli version: {}".format(__version__)) print("Neo4j version: {}".format(".".join(map(str, self.neo4j.neo4j_version)))) print("Bug reports: https://github.com/nicolewhite/cycli/issues\n") completer = CypherCompleter(labels, relationship_types, properties) layout = create_prompt_layout( lexer=CypherLexer, get_prompt_tokens=get_tokens, reserve_space_for_menu=8, ) buff = CypherBuffer( accept_action=AcceptAction.RETURN_DOCUMENT, history=FileHistory(filename=os.path.expanduser('~/.cycli_history')), completer=completer, complete_while_typing=True, ) application = Application( style=PygmentsStyle(CypherStyle), buffer=buff, layout=layout, on_exit=AbortAction.RAISE_EXCEPTION, key_bindings_registry=CypherBinder.registry ) cli = CommandLineInterface(application=application, eventloop=create_eventloop()) try: while True: document = cli.run() query = document.text self.handle_query(query) except UserWantsOut: print("Goodbye!") except Exception as e: print(e) def handle_query(self, query): run_n = re.match('run-([0-9]+) (.*)', query, re.DOTALL) save_csv = query.startswith("save-csv ") if self.cypher.is_a_write_query(query) and self.read_only: print("Query aborted. You are in read-only mode.") elif query in ["quit", "exit"]: raise UserWantsOut elif query == "help": print_help() elif query == "refresh": self.neo4j.refresh() elif query == "schema": self.neo4j.print_schema() elif query == "schema-indexes": self.neo4j.print_indexes() elif query == "schema-constraints": self.neo4j.print_constraints() elif query == "schema-labels": self.neo4j.print_labels() elif query == "schema-rels": self.neo4j.print_relationship_types() elif query.startswith("env"): if query == "env": for key, value in self.neo4j.parameters.items(): print("{0}={1}".format(key, value)) else: key = query[3:] key = key.strip("'\"[]") value = self.neo4j.parameters.get(key) if value is not None: print(value) elif query.startswith("export "): if "=" not in query: print("Set parameters with export key=value.") else: params = query.replace("export ", "").strip() key, value = params.split("=", 1) key = key.strip() value = value.strip() try: value = eval(value) self.neo4j.update_parameters(key, value) except Exception as e: print(e) else: count = int(run_n.group(1)) if run_n else 1 query = run_n.group(2) if run_n else query query = query[len("save-csv "):] if save_csv else query if count <= 0 or not query: print("Check your syntax. cycli expects run-{n} {query} where {n} is an integer > 0 and {query} is a Cypher query.") return total_duration = 0 index = 0 while index < count: response = self.neo4j.cypher(query) headers = response["headers"] rows = response["rows"] duration = response["duration"] error = response["error"] profile = response.get("profile") if error is False: print(pretty_table(headers, rows)) ms = "Run {}: {} ms\n".format(index + 1, duration) if run_n else "{} ms".format(duration) print(ms) if profile: self.neo4j.print_profile(profile) if save_csv: self.write_to_csvfile(headers, rows) else: print(error) if self.logfile: self.write_to_logfile(query, response) total_duration += duration index += 1 if run_n and error is False: print("Total duration: {} ms".format(total_duration))