Add jql validator

This commit is contained in:
Roland Thomas Jr 2024-06-08 13:12:11 -04:00
parent f8b74b5893
commit f299a13ba6
Signed by: roland
GPG Key ID: 7C3C2B085A4C2872
3 changed files with 214 additions and 51 deletions

5
.gitignore vendored
View File

@ -160,4 +160,7 @@ cython_debug/
#.idea/
# sqlite.db
*.db
*.db
config.json

View File

@ -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()
return session.prompt_async()

View File

@ -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()