Add jql validator
This commit is contained in:
parent
f8b74b5893
commit
f299a13ba6
|
@ -161,3 +161,6 @@ cython_debug/
|
||||||
|
|
||||||
# sqlite.db
|
# sqlite.db
|
||||||
*.db
|
*.db
|
||||||
|
|
||||||
|
config.json
|
||||||
|
|
||||||
|
|
|
@ -1,76 +1,236 @@
|
||||||
import pygments
|
import json
|
||||||
from pygments.lexer import RegexLexer, include
|
|
||||||
from pygments.token import Text, Keyword, Operator, Punctuation, Name, String, Whitespace
|
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 import PromptSession
|
||||||
from prompt_toolkit.lexers import PygmentsLexer
|
from prompt_toolkit.lexers import PygmentsLexer
|
||||||
from prompt_toolkit.styles import Style
|
from prompt_toolkit.styles import Style
|
||||||
from prompt_toolkit.completion import WordCompleter
|
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):
|
class JQLLexer(RegexLexer):
|
||||||
name = 'JQL'
|
name = "JQL"
|
||||||
aliases = ['jql']
|
aliases = ["jql"]
|
||||||
filenames = ['*.jql']
|
filenames = ["*.jql"]
|
||||||
|
|
||||||
tokens = {
|
tokens = {
|
||||||
'root': [
|
"root": [
|
||||||
(r'\s+', Whitespace),
|
(r"\s+", Whitespace),
|
||||||
(r'"', String, 'string'),
|
(r'"', String, "string"),
|
||||||
(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"(?i)\b(?:issueHistory|openSprints|watchedIssues|myApproval|myPending|currentLogin|currentUser|"
|
||||||
r'startOfYear|endOfYear)\b', Name.Function),
|
r"membersOf|lastLogin|now|startOfDay|endOfDay|startOfWeek|endOfWeek|startOfMonth|endOfMonth|"
|
||||||
(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"startOfYear|endOfYear)\b",
|
||||||
r'THERE|THESE|THEY|THIS|TO|WILL|WITH)\b', Keyword),
|
Name.Function,
|
||||||
(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"(?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'timespent|Voter|Watcher|affectedVersion)\b', Name.Attribute),
|
r"THERE|THESE|THEY|THIS|TO|WILL|WITH)\b",
|
||||||
(r'(?i)(=|>|>=|~|IN|IS|WAS|CHANGED|!=|<|<=|!~|NOT)', Operator),
|
Keyword,
|
||||||
(r'[\*\(/\^\.@;:+%#\[\|\?\),\$]]', Punctuation),
|
),
|
||||||
(r'[\w\.\-]+', Text),
|
(
|
||||||
|
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': [
|
"string": [
|
||||||
(r'"', String, '#pop'),
|
(r'"', String, "#pop"),
|
||||||
(r"'", String, '#pop'),
|
(r"'", String, "#pop"),
|
||||||
(r'[^"\']+', String),
|
(r'[^"\']+', String),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
nord_style = Style.from_dict({
|
|
||||||
'pygments.whitespace': '#FFFFFF',
|
nord_style = Style.from_dict(
|
||||||
'pygments.operator': '#EBCB8B',
|
{
|
||||||
'pygments.keyword': '#81A1C1 bold',
|
"pygments.whitespace": "#FFFFFF",
|
||||||
'pygments.punctuation': '#BF616A',
|
"pygments.keyword": "#81A1C1 bold",
|
||||||
'pygments.name.attribute': '#A3BE8C',
|
"pygments.operator": "#EBCB8B bold",
|
||||||
'pygments.name.function': '#B48EAD',
|
"pygments.punctuation": "#BF616A",
|
||||||
'pygments.string': '#D8DEE9',
|
"pygments.name.attribute": "#B48EAD",
|
||||||
'pygments.text': '#D8DEE9',
|
"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 = [
|
completions = [
|
||||||
'assignee', 'affectedVersion', 'attachments', 'comment', 'component', 'created', 'creator', 'description',
|
"assignee",
|
||||||
'due', 'duedate', 'filter', 'fixVersion', 'issuekey', 'labels', 'lastViewed', 'priority', 'project',
|
"affectedVersion",
|
||||||
'reporter', 'resolved', 'sprint', 'status', 'statusCategory', 'summary', 'text', 'timespent', 'voter', 'watcher',
|
"attachments",
|
||||||
'A', 'AND', 'ARE', 'AS', 'AT', 'BE', 'BUT', 'BY', 'FOR', 'IF', 'INTO', 'IT', 'NO', 'NOT', 'OF', 'ON', 'OR', 'S',
|
"comment",
|
||||||
'SUCH', 'T', 'THAT', 'THE', 'THEIR', 'THEN', 'THERE', 'THESE', 'THEY', 'THIS', 'TO', 'WILL', 'WITH',
|
"component",
|
||||||
'issueHistory', 'watchedIssues', 'myApproval', 'myPending', 'currentLogin', 'currentUser',
|
"created",
|
||||||
'membersOf', 'lastLogin', 'now', 'startOfDay', 'endOfDay', 'startOfWeek', 'endOfWeek', 'startOfMonth', 'endOfMonth',
|
"creator",
|
||||||
'startOfYear', 'endOfYear'
|
"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():
|
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:
|
while True:
|
||||||
try:
|
try:
|
||||||
user_input = session.prompt('Enter JQL: ')
|
user_input = session.prompt("Enter JQL: ", validate_while_typing=False)
|
||||||
print(f'You entered: {user_input}')
|
if user_input.lower() == "b":
|
||||||
|
continue
|
||||||
|
if user_input.lower() == "exit":
|
||||||
|
break
|
||||||
|
jql.print(user_input)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
continue
|
continue
|
||||||
except EOFError:
|
except EOFError:
|
||||||
break
|
break
|
||||||
print('Goodbye!')
|
console.print("Goodbye!", style="#BF616A bold")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
Loading…
Reference in New Issue