From f299a13ba6e31c0281e0a434eacdc53e5be0aa63 Mon Sep 17 00:00:00 2001 From: Roland Thomas Date: Sat, 8 Jun 2024 13:12:11 -0400 Subject: [PATCH] Add jql validator --- .gitignore | 5 +- cli/in_put.py | 2 +- cli/jql_pygments.py | 258 +++++++++++++++++++++++++++++++++++--------- 3 files changed, 214 insertions(+), 51 deletions(-) diff --git a/.gitignore b/.gitignore index 69ab14c..4f5dc48 100644 --- a/.gitignore +++ b/.gitignore @@ -160,4 +160,7 @@ cython_debug/ #.idea/ # sqlite.db -*.db \ No newline at end of file +*.db + +config.json + diff --git a/cli/in_put.py b/cli/in_put.py index b193704..e22f5d2 100755 --- a/cli/in_put.py +++ b/cli/in_put.py @@ -94,4 +94,4 @@ def confirm_async(message: str = "Confirm?", suffix: str = " (y/n) ") -> bool: Display a confirmation prompt that returns True/False. """ session = create_confirm_session(message, suffix) - return session.prompt_async() \ No newline at end of file + return session.prompt_async() diff --git a/cli/jql_pygments.py b/cli/jql_pygments.py index b3a8374..8dffb01 100644 --- a/cli/jql_pygments.py +++ b/cli/jql_pygments.py @@ -1,76 +1,236 @@ -import pygments -from pygments.lexer import RegexLexer, include -from pygments.token import Text, Keyword, Operator, Punctuation, Name, String, Whitespace +import json + +from pygments.lexer import RegexLexer +from pygments.token import ( + Text, + Keyword, + Operator, + Punctuation, + Name, + String, + Whitespace, + Error, +) from prompt_toolkit import PromptSession from prompt_toolkit.lexers import PygmentsLexer from prompt_toolkit.styles import Style from prompt_toolkit.completion import WordCompleter +from prompt_toolkit.validation import Validator, ValidationError +from rich.console import Console +from rich.text import Text as RichText +from jira import JIRA +from jira.exceptions import JIRAError + class JQLLexer(RegexLexer): - name = 'JQL' - aliases = ['jql'] - filenames = ['*.jql'] + name = "JQL" + aliases = ["jql"] + filenames = ["*.jql"] tokens = { - 'root': [ - (r'\s+', Whitespace), - (r'"', String, 'string'), - (r"'", String, 'string'), - (r'(?i)\b(?:issueHistory|openSprints|watchedIssues|myApproval|myPending|currentLogin|currentUser|' - r'membersOf|lastLogin|now|startOfDay|endOfDay|startOfWeek|endOfWeek|startOfMonth|endOfMonth|' - r'startOfYear|endOfYear)\b', Name.Function), - (r'(?i)\b(?:A|AND|ARE|AS|AT|BE|BUT|BY|FOR|IF|INTO|IT|NO|OF|ON|OR|S|SUCH|T|THAT|THE|THEIR|THEN|' - r'THERE|THESE|THEY|THIS|TO|WILL|WITH)\b', Keyword), - (r'(?i)\b(?:Assignee|affectedVersion|Attachments|Category|Comment|Component|Created|createdDate|' - r'Creator|Description|Due|duedate|Filter|fixVersion|issuekey|issuetype|issueLinkType|Labels|' - r'lastViewed|Priority|Project|Reporter|Resolved|Sprint|Status|statusCategory|Summary|Text|' - r'timespent|Voter|Watcher|affectedVersion)\b', Name.Attribute), - (r'(?i)(=|>|>=|~|IN|IS|WAS|CHANGED|!=|<|<=|!~|NOT)', Operator), - (r'[\*\(/\^\.@;:+%#\[\|\?\),\$]]', Punctuation), - (r'[\w\.\-]+', Text), + "root": [ + (r"\s+", Whitespace), + (r'"', String, "string"), + (r"'", String, "string"), + ( + r"(?i)\b(?:issueHistory|openSprints|watchedIssues|myApproval|myPending|currentLogin|currentUser|" + r"membersOf|lastLogin|now|startOfDay|endOfDay|startOfWeek|endOfWeek|startOfMonth|endOfMonth|" + r"startOfYear|endOfYear)\b", + Name.Function, + ), + ( + r"(?i)\b(?:A|AND|ARE|AS|AT|BE|BUT|BY|FOR|IF|INTO|IT|NO|OF|ON|OR|S|SUCH|T|THAT|THE|THEIR|THEN|" + r"THERE|THESE|THEY|THIS|TO|WILL|WITH)\b", + Keyword, + ), + ( + r"(?i)\b(?:Assignee|affectedVersion|Attachments|Category|Comment|Component|Created|createdDate|" + r"Creator|Description|Due|duedate|Filter|fixVersion|issuekey|issuetype|issueLinkType|Labels|" + r"lastViewed|Priority|Project|Reporter|Resolved|Sprint|Status|statusCategory|Summary|Text|" + r"timespent|Voter|Watcher|affectedVersion)\b", + Name.Attribute, + ), + (r"(?i)(=|!=|<|>|<=|>=|~|!~|IN|NOT IN|IS|IS NOT|WAS|WAS IN|WAS NOT IN|WAS NOT)", Operator), + (r"[\*\(/\^\.@;:+%#\[\|\?\),\$]", Punctuation), + (r"[\w\.\-]+", Text), ], - 'string': [ - (r'"', String, '#pop'), - (r"'", String, '#pop'), + "string": [ + (r'"', String, "#pop"), + (r"'", String, "#pop"), (r'[^"\']+', String), ], } -nord_style = Style.from_dict({ - 'pygments.whitespace': '#FFFFFF', - 'pygments.operator': '#EBCB8B', - 'pygments.keyword': '#81A1C1 bold', - 'pygments.punctuation': '#BF616A', - 'pygments.name.attribute': '#A3BE8C', - 'pygments.name.function': '#B48EAD', - 'pygments.string': '#D8DEE9', - 'pygments.text': '#D8DEE9', -}) + +nord_style = Style.from_dict( + { + "pygments.whitespace": "#FFFFFF", + "pygments.keyword": "#81A1C1 bold", + "pygments.operator": "#EBCB8B bold", + "pygments.punctuation": "#BF616A", + "pygments.name.attribute": "#B48EAD", + "pygments.name.function": "#A3BE8C", + "pygments.literal.string": "#D08770", + "pygments.text": "#D8DEE9", + } +) + + +token_styles = { + Whitespace: "#FFFFFF", + Keyword: "#81A1C1 bold", + Operator: "#EBCB8B bold", + Punctuation: "#BF616A", + Name.Attribute: "#B48EAD", + Name.Function: "#A3BE8C", + String: "#D08770", + Text: "#D8DEE9", + Error: "#BF616A bold", +} + completions = [ - 'assignee', 'affectedVersion', 'attachments', 'comment', 'component', 'created', 'creator', 'description', - 'due', 'duedate', 'filter', 'fixVersion', 'issuekey', 'labels', 'lastViewed', 'priority', 'project', - 'reporter', 'resolved', 'sprint', 'status', 'statusCategory', 'summary', 'text', 'timespent', 'voter', 'watcher', - 'A', 'AND', 'ARE', 'AS', 'AT', 'BE', 'BUT', 'BY', 'FOR', 'IF', 'INTO', 'IT', 'NO', 'NOT', 'OF', 'ON', 'OR', 'S', - 'SUCH', 'T', 'THAT', 'THE', 'THEIR', 'THEN', 'THERE', 'THESE', 'THEY', 'THIS', 'TO', 'WILL', 'WITH', - 'issueHistory', 'watchedIssues', 'myApproval', 'myPending', 'currentLogin', 'currentUser', - 'membersOf', 'lastLogin', 'now', 'startOfDay', 'endOfDay', 'startOfWeek', 'endOfWeek', 'startOfMonth', 'endOfMonth', - 'startOfYear', 'endOfYear' + "assignee", + "affectedVersion", + "attachments", + "comment", + "component", + "created", + "creator", + "description", + "due", + "duedate", + "filter", + "fixVersion", + "issuekey", + "labels", + "lastViewed", + "priority", + "project", + "reporter", + "resolved", + "sprint", + "status", + "statusCategory", + "summary", + "text", + "timespent", + "voter", + "watcher", + "A", + "AND", + "ARE", + "AS", + "AT", + "BE", + "BUT", + "BY", + "FOR", + "IF", + "INTO", + "IT", + "NO", + "NOT", + "OF", + "ON", + "OR", + "S", + "SUCH", + "T", + "THAT", + "THE", + "THEIR", + "THEN", + "THERE", + "THESE", + "THEY", + "THIS", + "TO", + "WILL", + "WITH", + "issueHistory", + "watchedIssues", + "myApproval", + "myPending", + "currentLogin", + "currentUser", + "membersOf", + "lastLogin", + "now", + "startOfDay", + "endOfDay", + "startOfWeek", + "endOfWeek", + "startOfMonth", + "endOfMonth", + "startOfYear", + "endOfYear", ] -completer = WordCompleter(completions, ignore_case=True) + +class JQLPrinter: + def __init__(self, console: Console): + self.console = console + + def print(self, text: str): + self.console.print(self.pygments_to_rich(text)) + + def pygments_to_rich(self, text): + tokens = list(JQLLexer().get_tokens(text)) + rich_text = RichText() + for token_type, value in tokens: + style = token_styles.get(token_type, "white") + rich_text.append(value, style=style) + return rich_text + + +class JQLValidator(Validator): + def __init__(self, jira_instance): + self.jira = jira_instance + + def validate(self, document): + text = document.text + if text.lower() == "b" or text.lower() == "exit": + return + try: + self.jira.search_issues(text, maxResults=1) + except JIRAError as error: + error_text = error.response.json().get("errorMessages", ["Unknown error"])[0] + raise ValidationError(message=f"[!] {error_text}", cursor_position=len(text)) + + +def create_jira_prompt_session(jira): + completer = WordCompleter(completions, ignore_case=True) + return PromptSession( + lexer=PygmentsLexer(JQLLexer), + style=nord_style, + completer=completer, + validator=JQLValidator(jira), + rprompt="[b] Back [exit] Exit", + ) + +with open("config.json") as json_file: + config = json.load(json_file) def main(): - session = PromptSession(lexer=PygmentsLexer(JQLLexer), style=nord_style, completer=completer) + console = Console(color_system="truecolor") + jira = JIRA(server=config["server"], basic_auth=(config["username"], config["token"])) + jql = JQLPrinter(console) + session = create_jira_prompt_session(jira) while True: try: - user_input = session.prompt('Enter JQL: ') - print(f'You entered: {user_input}') + user_input = session.prompt("Enter JQL: ", validate_while_typing=False) + if user_input.lower() == "b": + continue + if user_input.lower() == "exit": + break + jql.print(user_input) except KeyboardInterrupt: continue except EOFError: break - print('Goodbye!') + console.print("Goodbye!", style="#BF616A bold") -if __name__ == '__main__': + +if __name__ == "__main__": main()