Skip to content

Commit 514f34a

Browse files
committed
🚧 Support of issues management
- issue listing - issue get/set/toggle of labels, milestones and open-close status - issue edit of issue - parsing of notification mail to extract repo_slug and issue number fixes #104 Signed-off-by: Guyzmo <[email protected]>
1 parent 43999e6 commit 514f34a

File tree

7 files changed

+991
-2
lines changed

7 files changed

+991
-2
lines changed

Diff for: git_repo/repo.py

+202
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,22 @@
2929
{self} [--path=<path>] [-v...] <target> (gist|snippet) fetch <gist> [<gist_file>]
3030
{self} [--path=<path>] [-v...] <target> (gist|snippet) create [--secret] <description> [<gist_path> <gist_path>...]
3131
{self} [--path=<path>] [-v...] <target> (gist|snippet) delete <gist> [-f]
32+
{self} [--path=<path>] [-v...] <target> issue (list|ls) [--filter=<filter>]
33+
{self} [--path=<path>] [-v...] <target> issue (list|ls) [<action>|<issue_id>]
34+
{self} [--path=<path>] [-v...] <target> issue get <action> [--filter=<filter>] [<issue_id> <issue_id>...]
35+
{self} [--path=<path>] [-v...] <target> issue set <action> <value> [--filter=<filter>] [<issue_id> <issue_id>...]
36+
{self} [--path=<path>] [-v...] <target> issue unset <action> <value> [--filter=<filter>] [<issue_id> <issue_id>...]
37+
{self} [--path=<path>] [-v...] <target> issue toggle <action> <value> [--filter=<filter>] [<issue_id> <issue_id>...]
38+
{self} [--path=<path>] [-v...] <target> issue edit [<issue_id>]
39+
{self} [--path=<path>] [-v...] <target> issue <user>/<repo> (list|ls) [--filter=<filter>]
40+
{self} [--path=<path>] [-v...] <target> issue <user>/<repo> (list|ls) [<action>|<issue_id>]
41+
{self} [--path=<path>] [-v...] <target> issue <user>/<repo> get <action> [--filter=<filter>] [<issue_id> <issue_id>...]
42+
{self} [--path=<path>] [-v...] <target> issue <user>/<repo> set <action> <value> [--filter=<filter>] [<issue_id> <issue_id>...]
43+
{self} [--path=<path>] [-v...] <target> issue <user>/<repo> unset <action> <value> [--filter=<filter>] [<issue_id> <issue_id>...]
44+
{self} [--path=<path>] [-v...] <target> issue <user>/<repo> toggle <action> <value> [--filter=<filter>] [<issue_id> <issue_id>...]
45+
{self} [--path=<path>] [-v...] <target> issue <user>/<repo> edit [<issue_id>]
46+
{self} [--path=<path>] [-v...] <target> issue <user>/<repo> add <action> <value>
47+
{self} [--path=<path>] [-v...] <target> issue <user>/<repo> delete [-f] <action>
3248
{self} [--path=<path>] [-v...] <target> config [--config=<gitconfig>]
3349
{self} [-v...] config [--config=<gitconfig>]
3450
{self} --help
@@ -44,6 +60,7 @@
4460
list Lists the repositories for a given user
4561
gist Manages gist files
4662
request Handles requests for merge
63+
issue Handles issues
4764
open Open the given or current repository in a browser
4865
config Run authentication process and configure the tool
4966
@@ -92,6 +109,16 @@
92109
<title> Title to give to the request for merge
93110
-m,--message=<message> Description for the request for merge
94111
112+
Options for issues:
113+
get Gets a value for the given action listed below
114+
set Sets a value for the given action listed below
115+
unset Unsets a value for the given action listed below
116+
toggle Toggles a value for the given action listed below
117+
<action> Action: label, milestone or mark
118+
<value> Value for what shall be set
119+
--filter=<filter> Filters the list of issues [Default: '']
120+
<issue_id> Issue's number
121+
95122
Configuration options:
96123
alias Name to use for the git remote
97124
fqdn URL of the repository
@@ -146,6 +173,13 @@
146173

147174
EXTRACT_URL_RE = re.compile('[^:]*(://|@)[^/]*/')
148175

176+
def blue(s):
177+
return '\033[94m{}\033[0m'.format(s)
178+
def green(s):
179+
return '\033[92m{}\033[0m'.format(s)
180+
def red(s):
181+
return '\033[91m{}\033[0m'.format(s)
182+
149183
def confirm(what, where):
150184
'''
151185
Method to show a CLI based confirmation message, waiting for a yes/no answer.
@@ -262,6 +296,14 @@ def set_branch(self, branch):
262296

263297
self.branch = branch
264298

299+
@store_parameter('<action>')
300+
def set_action(self, action):
301+
self.action = action
302+
303+
@store_parameter('<issue_id>')
304+
def set_issue_action(self, issue_id):
305+
self.issues = issue_id
306+
265307
@store_parameter('<repo>')
266308
def set_target_repo(self, repo):
267309
self.target_repo = repo
@@ -492,6 +534,166 @@ def do_gist_delete(self):
492534
log.info('Successfully deleted gist!')
493535
return 0
494536

537+
'''Issues'''
538+
539+
@register_action('issue', 'ls')
540+
@register_action('issue', 'list')
541+
def do_issue_list(self):
542+
service = self.get_service()
543+
if self.action:
544+
if self.action in ('milestones', 'milestone', 'm'):
545+
milestones = service.issue_milestone_list(self.user_name, self.repo_name)
546+
print(blue(next(milestones)), file=sys.stderr)
547+
for milestone in milestones:
548+
print(milestone)
549+
return 0
550+
elif self.action in ('labels', 'label', 'l'):
551+
labels = service.issue_label_list(self.user_name, self.repo_name)
552+
print(blue(next(labels)), file=sys.stderr)
553+
for label in labels:
554+
print(label)
555+
return 0
556+
elif self.action in ('mark', 'm'):
557+
print('opened\nclosed\nread')
558+
return 0
559+
else:
560+
issue = service.issue_grab(self.user_name, self.repo_name, self.action)
561+
print('\n'.join([
562+
'Issue #{} ({}) by @{}'.format(
563+
issue['id'],
564+
green(issue['state']) if issue['state'] == 'open' else red(issue['state']),
565+
issue['poster']),
566+
'Created at:\t{} {}'.format(
567+
issue['creation'],
568+
'' if not issue['state'] == 'closed' else 'and closed at: {} by @{}'.format(
569+
issue['closed_at'], issue['closed_by']
570+
)
571+
),
572+
'Assigned:\t{}'.format('@{}'.format(issue['assignee']) or 'ø'),
573+
'Milestone:\t{}'.format(issue['milestone']),
574+
'Labels:\t\t{}'.format(', '.join(issue['labels'])),
575+
'URI:\t\t{}'.format(issue['uri']),
576+
'Title:\t\t{}'.format(issue['title']),
577+
'Body:', '',
578+
issue['body'],
579+
])
580+
)
581+
else:
582+
583+
584+
def format_issue(issue):
585+
if issue[0] == None:
586+
status_icon = ' '
587+
elif not issue[5]:
588+
status_icon = green('📖') if issue[0] else red('📕')
589+
else:
590+
status_icon = green('📦') if issue[0] else red('📦')
591+
number = issue[1].rjust(3)
592+
labels = issue[2][:20].ljust(20) + ("…" if len(issue[2]) > 20 else "")
593+
title = issue[3][:60].ljust(60) + ("…" if len(issue[3]) > 60 else "")
594+
uri = issue[4]
595+
return '{} {}\t{}\t{}\t{}'.format(status_icon, number, labels, title, uri)
596+
597+
issues = service.issue_list(self.user_name, self.repo_name, self.filter or '')
598+
print(blue(format_issue(next(issues))), file=sys.stderr)
599+
for issue in issues:
600+
print(format_issue(issue))
601+
return 0
602+
603+
@register_action('issue', 'get')
604+
def do_issue_get(self):
605+
service = self.get_service()
606+
if len(self.issues) == 1 and self.issues[0] == '-':
607+
self.user_name, self.repo_name, self.issues = service.issue_extract_from_file(sys.stdin)
608+
issue_data = service.issue_get(self.user_name, self.repo_name, self.action, self.filter, self.issues)
609+
print(blue(next(issue_data)), file=sys.stderr)
610+
for data in issue_data:
611+
print('{}'.format(data))
612+
613+
@register_action('issue', 'set')
614+
def do_issue_set(self):
615+
service = self.get_service()
616+
if len(self.issues) == 1 and self.issues[0] == '-':
617+
self.user_name, self.repo_name, self.issues = service.issue_extract_from_file(sys.stdin)
618+
if all(service.issue_set(self.user_name, self.repo_name, self.action, self.value, self.filter, self.issues)):
619+
return 0
620+
return 1
621+
622+
@register_action('issue', 'unset')
623+
def do_issue_unset(self):
624+
service = self.get_service()
625+
if len(self.issues) == 1 and self.issues[0] == '-':
626+
self.user_name, self.repo_name, self.issues = service.issue_extract_from_file(sys.stdin)
627+
if all(service.issue_unset(self.user_name, self.repo_name, self.action, self.value, self.filter, self.issues)):
628+
return 0
629+
return 1
630+
631+
@register_action('issue', 'toggle')
632+
def do_issue_toggle(self):
633+
service = self.get_service()
634+
if len(self.issues) == 1 and self.issues[0] == '-':
635+
self.user_name, self.repo_name, self.issues = service.issue_extract_from_file(sys.stdin)
636+
if all(service.issue_toggle(self.user_name, self.repo_name, self.action, self.value, self.filter, self.issues)):
637+
return 0
638+
return 1
639+
640+
@register_action('issue', 'edit')
641+
def do_issue_edit(self):
642+
do_ask=False
643+
if len(self.issues) == 1 and self.issues[0] == '-':
644+
self.user_name, self.repo_name, self.issues = service.issue_extract_from_file(sys.stdin)
645+
do_ask=True
646+
647+
def edit_issue(title, body):
648+
from tempfile import NamedTemporaryFile
649+
from subprocess import call
650+
with NamedTemporaryFile(
651+
prefix='git-repo-issue-',
652+
suffix='.md',
653+
mode='w+b') as issue_file:
654+
issue_file.write('Title: {}\n\nBody:\n{}\n'.format(title, body).encode('utf-8'))
655+
issue_file.flush()
656+
call("{} {}".format(os.environ['EDITOR'], issue_file.name), shell=True)
657+
issue_file.seek(0)
658+
updated_issue = issue_file.read().decode('utf-8')
659+
try:
660+
_, updated_issue = updated_issue.split('Title: ')
661+
title, body, *tail = updated_issue.split('\n\nBody:\n')
662+
body = ''.join([body]+tail)
663+
except Exception:
664+
raise ResourceError("Format of the modified issue cannot be parsed.")
665+
666+
print('New issue\'s details:')
667+
print('Title: {}'.format(title))
668+
print('Body:\n{}'.format(body))
669+
if do_ask and input('Do you confirm it\'s ok? [Yn] ').lower().startswith('n'):
670+
return None
671+
return {'title': title, 'body': body}
672+
673+
service = self.get_service()
674+
if service.issue_edit(self.user_name, self.repo_name, self.issues[0], edit_issue):
675+
return 0
676+
return 1
677+
678+
@register_action('issue', 'add')
679+
def do_issue_action_add(self):
680+
service = self.get_service()
681+
if service.issue_action_add(self.user_name, self.repo_name, self.action, self.value):
682+
return 0
683+
return 1
684+
685+
@register_action('issue', 'del')
686+
def do_issue_action_delete(self):
687+
service = self.get_service()
688+
if not self.force: # pragma: no cover
689+
if not confirm('Action {} will be removed'.format(self.action), self.repo_slug):
690+
return 0
691+
if service.issue_action_del(self.user_name, self.repo_name, self.action, self.value):
692+
return 0
693+
return 1
694+
695+
'''Configuration'''
696+
495697
@register_action('config')
496698
def do_config(self):
497699
from getpass import getpass

0 commit comments

Comments
 (0)