MasterofJOKers 2 years ago
parent d4491cf7ab
commit 14a9af7e51

@ -1,15 +1,25 @@
import configparser
import functools
import os
from pathlib import Path
from textwrap import indent, shorten, wrap
from typing import Any, Callable, Dict
import click
import editor
config = {
'host': 'http://localhost:2990/jira/',
'user': 'admin',
'password': 'admin'
}
CONFIG_PATH = Path(os.environ.get('XDG_CONFIG_HOME', '~/.config')).expanduser() / 'jiracli' / 'config'
def get_config():
config = configparser.ConfigParser()
config.read(CONFIG_PATH)
if not config.has_section('server'):
raise ValueError(f"Config in {CONFIG_PATH} does not contain a section [server]")
return config
def ipython():
@ -23,7 +33,35 @@ def main(ctx):
from jira import JIRA
ctx.ensure_object(dict)
ctx.obj['client'] = JIRA(config['host'], basic_auth=(config['user'], config['password']))
config = get_config()
ctx.obj['config'] = config
server_config = config['server']
skip_cert_verify = server_config.getboolean('skip_cert_verify', False)
if skip_cert_verify:
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
jira_kwargs = dict(
options={
'server': server_config['host'],
'verify': not skip_cert_verify
},
timeout=server_config.getint('timeout', 2),
max_retries=server_config.getint('max_retries', 2)
)
if 'password' in server_config:
if 'user' not in server_config:
raise ValueError('If password is set in [server], user needs to be set, too.')
jira_kwargs['basic_auth'] = (server_config['user'], server_config['password'])
elif 'token' in server_config:
jira_kwargs['token_auth'] = server_config['token']
else:
raise ValueError("Need either user and password or token in [server] for login.")
ctx.obj['client'] = JIRA(**jira_kwargs)
def pass_client(fn):
@ -35,6 +73,15 @@ def pass_client(fn):
return wrapped
def pass_config(fn):
@functools.wraps(fn)
@click.pass_obj
def wrapped(obj, *args, **kwargs):
config = obj['config']
return fn(config, *args, **kwargs)
return wrapped
@main.command()
@pass_client
def version(client):
@ -42,15 +89,25 @@ def version(client):
@main.command()
@click.option('--project', help='Project to filter for. Default: take from config [filters]/default_project')
@click.option('--status', multiple=True, help='Filter for issues with *any* of the given statuses')
@click.option('--label', 'labels', multiple=True, help='Filter for issues with all the given labels')
@click.option('--text', 'texts', multiple=True, help='Filter for issues containing all the given strings')
@click.option('--order', help='Sort by this field')
@click.option('--asc/--desc', 'asc', help='Sort ascending/descending. Default: descending')
@click.option('--jql', default='', help='Provide your own JQL query to search for issues. '
'Setting this ignores all other query options.')
'Setting this ignores all other query options except --project.')
@pass_client
def issues(client, status, labels, texts, order, asc, jql):
@pass_config
def issues(config, client, project, status, labels, texts, order, asc, jql):
filters_config = config.get('filters')
if not project and filters_config and 'default_project' in filters_config:
project = filters_config['default_project']
# TODO add filtering by created since
# TODO add filtering by updated
# TODO support pagination I guess? i.e. a limit
if not jql:
filters = []
@ -71,6 +128,9 @@ def issues(client, status, labels, texts, order, asc, jql):
if order:
jql += f" ORDER BY {order} {'ASC' if asc else 'DESC'}"
if project:
jql = f"project = {project} AND {jql}"
print(f"Searching with query: {jql}")
issues = client.search_issues(jql)
for issue in issues:
@ -193,13 +253,14 @@ def link():
@pass_client
def link_list(client, issue):
issuelinks = issue.fields.issuelinks
issuelinks = getattr(issue.fields, 'issuelinks', [])
if issuelinks:
print("\nIssue Links")
# TODO group by type?
for issuelink in issuelinks:
summary = indent('\n'.join(wrap(issuelink.inwardIssue.fields.summary, width=72)), prefix=' ' * 4)
print(f" {issuelink.type} - {issuelink.inwardIssue.key}\n{summary}")
other_issue = getattr(issuelink, 'inwardIssue', issuelink.outwardIssue)
summary = indent('\n'.join(wrap(other_issue.fields.summary, width=72)), prefix=' ' * 4)
print(f" {issuelink.type} - {other_issue.key}\n{summary}")
remote_links = client.remote_links(issue.id)
if remote_links:
@ -209,7 +270,7 @@ def link_list(client, issue):
print(f" {link.object.title} => {link.object.url}")
def prompt(description: str, *, multiline_ok: bool = True, initial_content: str = '') -> str:
def prompt(description: str, *, multiline_ok: bool = True, initial_content: str = '', empty_ok: bool = False) -> str:
divider_line = '--- Everything after this line will be removed'
contents = f"{initial_content}\n{divider_line}\n\n{description}"
@ -229,7 +290,7 @@ def prompt(description: str, *, multiline_ok: bool = True, initial_content: str
# TODO build a retry into this?
raise click.ClickException('Input needs to be a single line only')
if not contents_lines:
if not empty_ok and not any(contents_lines):
raise click.ClickException('Empty content. Cannot continue.')
return '\n'.join(contents_lines)
@ -243,7 +304,7 @@ def prompt(description: str, *, multiline_ok: bool = True, initial_content: str
def link_create(client, issue, url: str, title: str):
if url is None:
url = prompt(f"Input the URL for the link to add to {issue.key}")
url = prompt(f"Input the URL for the link to add to {issue.key}", multiline_ok=False)
if title is None:
title = prompt(f"Input a single line of title for {url}. This will be added to {issue.key}.",
multiline_ok=False)
@ -288,7 +349,8 @@ def label_edit(issue):
original_labels = issue.fields.labels
# TODO add a configurable list of "often used" labels into the description
labels = set(prompt('Add or remove labels by adding them line by line or removing one',
initial_content='\n'.join(original_labels)).splitlines())
initial_content='\n'.join(original_labels),
empty_ok=True).splitlines())
original_labels = set(original_labels)
@ -308,3 +370,17 @@ def label_edit(issue):
# inspired by jira.Issue.add_field_value
jira.resources.Resource.update(issue, fields={"update": {"labels": changes}})
@issue.command()
@pass_issue
def debug(issue):
import IPython
IPython.embed
@issue.command()
@pass_issue
def show(issue):
"""Show a lot of information about the issue"""
raise NotImplementedError

Loading…
Cancel
Save