Skip to content

Commit 1957bdf

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 - tests of the whole fixes #104 Signed-off-by: Guyzmo <guyzmo+github+pub@m0g.net>
1 parent 43999e6 commit 1957bdf

File tree

78 files changed

+38814
-75
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

78 files changed

+38814
-75
lines changed

git_repo/repo.py

+221
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> [--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,185 @@ 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+
def check_issues_parameter(self):
604+
if self.issues == [] and self.filter == '':
605+
if self.value:
606+
self.issues = [self.value]
607+
self.value = None
608+
else:
609+
raise ArgumentError("Need at least one issue or a --filter parameter")
610+
if len(self.issues) == 1 and self.issues[0] in ('*', 'all'):
611+
self.issues = []
612+
613+
@register_action('issue', 'get')
614+
def do_issue_get(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+
issue_data = service.issue_get(self.user_name, self.repo_name, self.action, self.filter or '', self.issues)
619+
print(blue(next(issue_data)), file=sys.stderr)
620+
for data in issue_data:
621+
print('{}'.format(data))
622+
623+
@register_action('issue', 'set')
624+
def do_issue_set(self):
625+
self.check_issues_parameter()
626+
service = self.get_service()
627+
if len(self.issues) == 1 and self.issues[0] == '-':
628+
self.user_name, self.repo_name, self.issues = service.issue_extract_from_file(sys.stdin)
629+
rv = 1
630+
if all(service.issue_set(self.user_name, self.repo_name, self.action, self.value, self.filter or '', self.issues)):
631+
rv = 0
632+
self.do_issue_get()
633+
return rv
634+
635+
@register_action('issue', 'unset')
636+
def do_issue_unset(self):
637+
self.check_issues_parameter()
638+
service = self.get_service()
639+
if len(self.issues) == 1 and self.issues[0] == '-':
640+
self.user_name, self.repo_name, self.issues = service.issue_extract_from_file(sys.stdin)
641+
rv = 1
642+
if all(service.issue_unset(self.user_name, self.repo_name, self.action, self.value, self.filter or '', self.issues)):
643+
rv = 0
644+
self.do_issue_get()
645+
return rv
646+
647+
@register_action('issue', 'toggle')
648+
def do_issue_toggle(self):
649+
self.check_issues_parameter()
650+
service = self.get_service()
651+
if len(self.issues) == 1 and self.issues[0] == '-':
652+
self.user_name, self.repo_name, self.issues = service.issue_extract_from_file(sys.stdin)
653+
rv = 1
654+
if all(service.issue_toggle(self.user_name, self.repo_name, self.action, self.value, self.filter or '', self.issues)):
655+
rv = 0
656+
self.do_issue_get()
657+
return rv
658+
659+
@register_action('issue', 'edit')
660+
def do_issue_edit(self):
661+
do_ask=False
662+
if len(self.issues) == 1 and self.issues[0] == '-':
663+
self.user_name, self.repo_name, self.issues = service.issue_extract_from_file(sys.stdin)
664+
do_ask=True
665+
666+
def edit_issue(title, body):
667+
from tempfile import NamedTemporaryFile
668+
from subprocess import call
669+
with NamedTemporaryFile(
670+
prefix='git-repo-issue-',
671+
suffix='.md',
672+
mode='w+b') as issue_file:
673+
issue_file.write('Title: {}\n\nBody:\n{}\n'.format(title, body).encode('utf-8'))
674+
issue_file.flush()
675+
call("{} {}".format(os.environ['EDITOR'], issue_file.name), shell=True)
676+
issue_file.seek(0)
677+
updated_issue = issue_file.read().decode('utf-8')
678+
try:
679+
_, updated_issue = updated_issue.split('Title: ')
680+
title, body, *tail = updated_issue.split('\n\nBody:\n')
681+
body = ''.join([body]+tail)
682+
except Exception:
683+
raise ResourceError("Format of the modified issue cannot be parsed.")
684+
685+
print('New issue\'s details:')
686+
print('Title: {}'.format(title))
687+
print('Body:\n{}'.format(body))
688+
if do_ask and input('Do you confirm it\'s ok? [Yn] ').lower().startswith('n'):
689+
return None
690+
return {'title': title, 'body': body}
691+
692+
service = self.get_service()
693+
if service.issue_edit(self.user_name, self.repo_name, self.issues[0], edit_issue):
694+
return 0
695+
return 1
696+
697+
@register_action('issue', 'add')
698+
def do_issue_action_add(self):
699+
service = self.get_service()
700+
if service.issue_action_add(self.user_name, self.repo_name, self.action, self.value):
701+
return 0
702+
return 1
703+
704+
@register_action('issue', 'del')
705+
def do_issue_action_delete(self):
706+
service = self.get_service()
707+
if not self.force: # pragma: no cover
708+
if not confirm('Action {} will be removed'.format(self.action), self.repo_slug):
709+
return 0
710+
if service.issue_action_del(self.user_name, self.repo_name, self.action, self.value):
711+
return 0
712+
return 1
713+
714+
'''Configuration'''
715+
495716
@register_action('config')
496717
def do_config(self):
497718
from getpass import getpass

0 commit comments

Comments
 (0)