| 
3 | 3 | import logging  | 
4 | 4 | log = logging.getLogger('git_repo.github')  | 
5 | 5 | 
 
  | 
6 |  | -from ..service import register_target, RepositoryService, os  | 
7 |  | -from ...exceptions import ResourceError, ResourceExistsError, ResourceNotFoundError  | 
 | 6 | +from ..service import register_target, RepositoryService, os, parse_comma_string_to_list  | 
 | 7 | +from ...exceptions import ResourceError, ResourceExistsError, ResourceNotFoundError, ArgumentError  | 
8 | 8 | 
 
  | 
9 | 9 | import github3  | 
10 | 10 | 
 
  | 
11 | 11 | from git.exc import GitCommandError  | 
 | 12 | +from collections import namedtuple  | 
 | 13 | + | 
12 | 14 | 
 
  | 
13 | 15 | @register_target('hub', 'github')  | 
14 | 16 | class GithubService(RepositoryService):  | 
@@ -304,6 +306,184 @@ def request_fetch(self, user, repo, request, pull=False, force=False):  | 
304 | 306 |                 raise ResourceNotFoundError('Could not find opened request #{}'.format(request)) from err  | 
305 | 307 |             raise err  | 
306 | 308 | 
 
  | 
 | 309 | +    '''Issues'''  | 
 | 310 | + | 
 | 311 | +    ISSUE_FILTER_DEFAULTS=dict(  | 
 | 312 | +        state     = 'all',  | 
 | 313 | +        milestone = None,  | 
 | 314 | +        assignee  = None,  | 
 | 315 | +        mentioned = None,  | 
 | 316 | +        labels    = [],  | 
 | 317 | +        sort      = None,  | 
 | 318 | +        direction = 'desc',  | 
 | 319 | +        since     = None,  | 
 | 320 | +    )  | 
 | 321 | + | 
 | 322 | +    def issue_list_parse_filter_statement(self, filter_stmt, transform=None):  | 
 | 323 | +        from copy import deepcopy  | 
 | 324 | + | 
 | 325 | +        params = deepcopy(self.ISSUE_FILTER_DEFAULTS)  | 
 | 326 | + | 
 | 327 | +        for f in parse_comma_string_to_list(filter_stmt):  | 
 | 328 | +            if ':' in f:  | 
 | 329 | +                param, value_head, *value_tail = f.split(':')  | 
 | 330 | +                value = "".join([value_head] + value_tail) # fix labels containing :  | 
 | 331 | +                if transform:  | 
 | 332 | +                    param, value = transform(param, value)  | 
 | 333 | +                if not param in params.keys():  | 
 | 334 | +                    raise ArgumentError('Unknown filter key {}'.format(param))  | 
 | 335 | +                if isinstance(params[param], list):  | 
 | 336 | +                    params[param].append(value)  | 
 | 337 | +                else:  | 
 | 338 | +                    params[param] = value  | 
 | 339 | +        return params  | 
 | 340 | + | 
 | 341 | +    def issue_extract_from_file(self, it):  | 
 | 342 | +        # Message-ID: <guyzmo/git-repo/issues/1/[email protected]>  | 
 | 343 | +        for line in it:  | 
 | 344 | +            if line.lower().startswith('message-id:'):  | 
 | 345 | +                _, line = line.lower().split('message-id: <')  | 
 | 346 | +                user, repo, _, issue, *_ = line.lower().split('/')  | 
 | 347 | +                return user, repo, [issue]  | 
 | 348 | + | 
 | 349 | +    def issue_label_list(self, user, repo):  | 
 | 350 | +        repository = self.gh.repository(user, repo)  | 
 | 351 | +        yield ("Name",)  | 
 | 352 | +        return [(yield l.name) for l in repository.iter_labels()]  | 
 | 353 | + | 
 | 354 | +    def issue_milestone_list(self, user, repo):  | 
 | 355 | +        repository = self.gh.repository(user, repo)  | 
 | 356 | +        yield ("Name",)  | 
 | 357 | +        return [(yield l.title) for l in repository.iter_milestones()]  | 
 | 358 | + | 
 | 359 | +    def issue_grab(self, user, repo, issue_id):  | 
 | 360 | +        repository = self.gh.repository(user, repo)  | 
 | 361 | +        issue = repository.issue(issue_id)  | 
 | 362 | +        return dict(  | 
 | 363 | +            id=issue.number,  | 
 | 364 | +            state=issue.state,  | 
 | 365 | +            title=issue.title,  | 
 | 366 | +            uri=issue.html_url,  | 
 | 367 | +            poster=issue.user.login,  | 
 | 368 | +            milestone=issue.milestone,  | 
 | 369 | +            labels=[label.name for label in issue.labels],  | 
 | 370 | +            creation=issue.created_at.isoformat(),  | 
 | 371 | +            closed_at=issue.closed_at,  | 
 | 372 | +            closed_by=issue.closed_by,  | 
 | 373 | +            body=issue.body,  | 
 | 374 | +            assignee=issue.assignee.login if issue.assignee else None,  | 
 | 375 | +            repository='/'.join(issue.repository)  | 
 | 376 | +        )  | 
 | 377 | + | 
 | 378 | +    def issue_list(self, user, repo, filter_str=''):  | 
 | 379 | +        params = self.issue_list_parse_filter_statement(  | 
 | 380 | +            filter_stmt=filter_str,  | 
 | 381 | +            transform=lambda k,v: (k.replace('status', 'state').replace('label', 'labels'), v)  | 
 | 382 | +        )  | 
 | 383 | + | 
 | 384 | +        repository = self.gh.repository(user, repo)  | 
 | 385 | +        yield (None, "Id", "Labels", "Title", "URL")  | 
 | 386 | +        for issue in repository.iter_issues(**params):  | 
 | 387 | +            yield ( not issue.is_closed(),  | 
 | 388 | +                    str(issue.number),  | 
 | 389 | +                    ','.join([l.name for l in issue.labels]),  | 
 | 390 | +                    issue.title,  | 
 | 391 | +                    issue.html_url,  | 
 | 392 | +                    issue.pull_request)  | 
 | 393 | + | 
 | 394 | +    def issue_edit(self, user, repo, issue, edit_cb):  | 
 | 395 | +        repository = self.gh.repository(user, repo)  | 
 | 396 | +        issue_obj = repository.issue(issue)  | 
 | 397 | +        updated_issue = edit_cb(issue_obj.title, issue_obj.body)  | 
 | 398 | +        if not updated_issue:  | 
 | 399 | +            return False  | 
 | 400 | +        return issue_obj.edit(title=updated_issue['title'], body=updated_issue['body'])  | 
 | 401 | + | 
 | 402 | +    def issue_action(self, user, repo, action, value, filter_str, issues, application):  | 
 | 403 | +        repository = self.gh.repository(user, repo)  | 
 | 404 | +        params = self.issue_list_parse_filter_statement(  | 
 | 405 | +            filter_stmt=filter_str,  | 
 | 406 | +            transform=lambda k,v: (k.replace('label', 'labels'), v)  | 
 | 407 | +        )  | 
 | 408 | +        for issue in repository.iter_issues(**params):  | 
 | 409 | +            if not issues or str(issue.number) in issues:  | 
 | 410 | +                if action == "mark":  | 
 | 411 | +                    if value.lower() in ('opened', 'open', 'o'):  | 
 | 412 | +                        return application['mark'](issue, opened=True)  | 
 | 413 | +                    elif value.lower() in ('closed', 'close', 'c'):  | 
 | 414 | +                        return application['mark'](issue, opened=False)  | 
 | 415 | + | 
 | 416 | +                if action == "label":  | 
 | 417 | +                    labels = set()  | 
 | 418 | +                    labels_avail = {l.name: l for l in repository.iter_labels()}  | 
 | 419 | +                    for label in parse_comma_string_to_list(value):  | 
 | 420 | +                        if label in labels_avail:  | 
 | 421 | +                            labels.add(labels_avail[label])  | 
 | 422 | +                        else:  | 
 | 423 | +                            raise ArgumentError("Label '{}' is invalid.".format(value))  | 
 | 424 | +                    return application['label'](issue, list(labels))  | 
 | 425 | + | 
 | 426 | +                if action == "milestone":  | 
 | 427 | +                    milestones = list(repository.iter_milestones())  | 
 | 428 | +                    for milestone in milestones:  | 
 | 429 | +                        if value == milestone.title:  | 
 | 430 | +                            return application['milestone'](issue, milestone)  | 
 | 431 | +                    raise ArgumentError("Milestone '{}' is invalid.".format(value))  | 
 | 432 | + | 
 | 433 | +    def issue_set(self, user, repo, action, value, filter_str, issues):  | 
 | 434 | +        def set_mark(issue, opened):  | 
 | 435 | +            if opened:  | 
 | 436 | +                return issue.reopen()  | 
 | 437 | +            return issue.close()  | 
 | 438 | +        def add_labels(issue, labels):  | 
 | 439 | +            return issue.add_labels(*[l.name for l in labels])  | 
 | 440 | +        def set_milestone(issue, milestone):  | 
 | 441 | +            return issue.edit(milestone=milestone.number)  | 
 | 442 | + | 
 | 443 | +        return self.issue_action(user, repo, action, value, filter_str, issues, dict(  | 
 | 444 | +                mark=set_mark,  | 
 | 445 | +                label=add_labels,  | 
 | 446 | +                milestone=set_milestone  | 
 | 447 | +            )  | 
 | 448 | +        )  | 
 | 449 | + | 
 | 450 | +    def issue_unset(self, user, repo, action, value, filter_str, issues):  | 
 | 451 | +        def unset_mark(issue, opened):  | 
 | 452 | +            raise ArgumentError('Cannot unset marks.')  | 
 | 453 | +        def remove_labels(issue, labels):  | 
 | 454 | +            for l in labels:  | 
 | 455 | +                if not issue.remove_label(l.name):  | 
 | 456 | +                    return False  | 
 | 457 | +        def unset_milestone(issue, milestone):  | 
 | 458 | +            return issue.edit(milestone=0)  | 
 | 459 | + | 
 | 460 | +        return self.issue_action(user, repo, action, value, filter_str, issues, dict(  | 
 | 461 | +                mark=unset_mark,  | 
 | 462 | +                label=remove_labels,  | 
 | 463 | +                milestone=unset_milestone  | 
 | 464 | +            )  | 
 | 465 | +        )  | 
 | 466 | + | 
 | 467 | +    def issue_toggle(self, user, repo, action, filter_str, issues):  | 
 | 468 | +        def toggle_mark(issue, opened):  | 
 | 469 | +            if issue.is_closed():  | 
 | 470 | +                return issue.reopen()  | 
 | 471 | +            return issue.close()  | 
 | 472 | +        def toggle_labels(issue, labels):  | 
 | 473 | +            issue_labels = set(issue.iter_labels()).symmetric_difference(labels)  | 
 | 474 | +            return issue.replace_labels(*[l.name for l in labels])  | 
 | 475 | +        def set_milestone(issue, milestone):  | 
 | 476 | +            if issue.milestone:  | 
 | 477 | +                return issue.edit(milestone=0)  | 
 | 478 | +            return issue.edit(milestone=milestone.number)  | 
 | 479 | + | 
 | 480 | +        return self.issue_action(user, repo, action, filter_str, issues, dict(  | 
 | 481 | +                mark=unset_mark,  | 
 | 482 | +                label=remove_labels,  | 
 | 483 | +                milestone=unset_milestone  | 
 | 484 | +            )  | 
 | 485 | +        )  | 
 | 486 | + | 
307 | 487 |     @classmethod  | 
308 | 488 |     def get_auth_token(cls, login, password, prompt=None):  | 
309 | 489 |         import platform  | 
 | 
0 commit comments