def update(number, params={}): assigneedic = assignees() trackerdic = trackers() statusdic = statuses() comment = u'\n'.join( (u'Available assignees: {0}', u'Available labels: {1}', u'Available priorities: 3-7 (low to high)', u'Available states: {2}')).format( u', '.join(x['login'] for x in assigneedic.itervalues()), u', '.join(trackerdic.itervalues()), u', '.join(statusdic.itervalues())) tic, _ = issue(number, params) tic.priority = tic.priority_id # FIXME: monkey patch template = ticket.template( ('title', 'assignee', 'labels', 'priority', 'state', 'body', 'notes'), tic, comment=comment) val = util.inputwitheditor(template) if val == template: return data = _issuedata_from_template(val) cfg = config.parseconfig() _request('put', ISSUE.format(issueid=number, **cfg), data=json.dumps(data), params=params, headers={'content-type': 'application/json'})
def add(params={}): assigneedic = assignees() trackerdic = trackers() statusdic = statuses() comment = u'\n'.join( (u'Available assignees: {0}', u'Available labels: {1}', u'Available priorities: 3-7 (low to high)', u'Available states: {2}')).format( u', '.join(x['login'] for x in assigneedic.itervalues()), u', '.join(trackerdic.itervalues()), u', '.join(statusdic.itervalues())) template = ticket.template( ('title', 'assignee', 'labels', 'priority', 'state', 'body'), comment=comment) val = util.inputwitheditor(template) if val == template: return data = _issuedata_from_template(val) cfg = config.parseconfig() data['issue']['project_id'] = cfg['repo'] r = _request('post', ISSUES.format(**cfg), data=json.dumps(data), params=params, headers={'content-type': 'application/json'})['issue'] return { 'number': r['id'], 'html_url': ISSUE_URL.format(issueid=r['id'], **cfg) }
def user(n): cfg = config.parseconfig() r = _request('get', USER.format(userid=n, **cfg)) if r: return r['user'] else: return {'id':n, 'login':n, 'firstname':'unknown', 'lastname':'unknown'}
def issue(number, params={}): cfg = config.parseconfig() params['include'] = u','.join(('journals', 'children', 'changesets')) r = _request('get', ISSUE.format(issueid=number, **cfg), params=params)['issue'] comments = [ticket.Comment(**_parse_journal(x)) for x in reversed(r['journals'])] tic = _toticket(r) return tic, comments
def update(opts): cfg = config.parseconfig() r = cfg['service'].update(opts['number']) if r is None: print u'Abort. Canceled to update a ticket for some reason.' else: term = blessings.Terminal() print u'Ticket {term.green}#{0} was successfully updated.'.format(opts['number'], term=term)
def changestate(number, state): if state not in ('open', 'closed'): raise ValueError('Unknown state: {0}'.format(state)) data = {'state': state} cfg = config.parseconfig() _request('patch', ISSUE.format(issueid=number, **cfg), data=json.dumps(data))
def list(opts): cfg = config.parseconfig() tickets = cfg['service'].issues(opts) if not tickets: print u'No open tickets.\n' else: for tic in tickets: print tic.format(cfg['format_list'])
def list(opts): cfg = config.parseconfig() tickets = cfg['service'].issues(opts) if not tickets: print u'No tickets.\n' else: for tic in tickets: print tic.format(cfg['format_list'])
def add(opts): cfg = config.parseconfig() r = cfg['service'].add() if r is None: print u'Abort. Canceled to add a ticket for some reason.' else: term = blessings.Terminal() print u'Added {term.green}#{0}{term.normal}\nURL: {1}\n'.format(r['number'], r['html_url'], term=term)
def commentto(number, params={}): template = """# comment below here\n""" val = util.inputwitheditor(template) data = {'body': util.rmcomment(val)} cfg = config.parseconfig() _request('post', ISSUE_COMMENTS.format(issueid=number, **cfg), data=json.dumps(data), params=params)
def comments(number, params={}): cfg = config.parseconfig() r = _request('get', ISSUE_COMMENTS.format(issueid=number, **cfg), params=params) comments = [ticket.Comment(number = x['id'], body = x['body'], creator = nested_access(x, 'user.login'), created = todatetime(x['created_at']), updated = todatetime(x['updated_at'])) for x in r] return comments
def changestate(number, state): if state == 'closed': state = 'resolved' avail_states = ('new', 'open', 'resolved', 'on hold', 'invalid', 'duplicate', 'wontfix') if state not in avail_states: raise ValueError('Invarid query: available state are ({0})'.format(u', '.join(avail_states))) data = {'status': state} cfg = config.parseconfig() _request('put', ISSUE.format(issueid=number, **cfg), data=data)
def authorize(name, pswd): cfg = config.parseconfig() r = requests.post(AUTH, data=json.dumps({ 'scopes': ['repo'], 'note': 'git-ticket' }), auth=(name, pswd), verify=cfg['sslverify']) return r.json
def commentto(number, params={}): template = """# comment below here\n""" val = util.inputwitheditor(template) data = {'notes': util.rmcomment(val)} cfg = config.parseconfig() _request('put', ISSUE.format(issueid=number, **cfg), data=json.dumps(data), params=params, headers={'content-type': 'application/json'})
def github_auth(opts): import getpass from gitticket import github cfg = config.parseconfig() pswd = getpass.getpass('github password for {0}: '.format(cfg['name'])) r = github.authorize(cfg['name'], pswd) if 'message' in r: sys.exit(r['message']) print 'You got an access token: {0}'.format(r['token']) print 'If you want to set global, type:\ngit config --global ticket.github.token {0}'.format(r['token'])
def update(number, params={}): tic = issue(number, params) comment = u'Available labels (select one): bug, enhancement, proposal, task\nAvailable priorities: trivial, minor, major, critical, blocker' template = ticket.template(('title', 'assignee', 'labels', 'state', 'priority', 'milestone', 'version', 'component', 'body'), tic, comment=comment) val = util.inputwitheditor(template) if val == template: return data = _issuedata_from_template(val) cfg = config.parseconfig() _request('put', ISSUE.format(issueid=number, **cfg), data=data, params=params)
def issues(params={}): cfg = config.parseconfig() url = ISSUES.format(**cfg) if 'state' in params and params['state'] not in ('open', 'closed'): raise ValueError('Invarid query: available state are (open, closed)') if 'order' in params: params['sort'] = params.pop('order') r = _request('get', url, params=params) tickets = [_toticket(x) for x in r] return tickets
def add(params={}): comment = u'Available labels (select one): bug, enhancement, proposal, task\nAvailable priorities: trivial, minor, major, critical, blocker' template = ticket.template(('title', 'assignee', 'labels', 'priority', 'milestone', 'version', 'component', 'body'), comment=comment) val = util.inputwitheditor(template) if val == template: return data = _issuedata_from_template(val) cfg = config.parseconfig() r = _request('post', ISSUES.format(**cfg), data=data, params=params) return {'number': r['local_id'], 'html_url': ISSUEURL.format(issueid=r['local_id'], **cfg)}
def update(number, params={}): tic = issue(number, params) comment = 'Available assignees: {0}\nAvailable labels: {1}'.format(u', '.join(labels()), u', '.join(assignees())) template = ticket.template(('title', 'state', 'assignee', 'labels', 'milestone_id', 'body'), tic, comment=comment) val = util.inputwitheditor(template) if val == template: return data = _issuedata_from_template(val) cfg = config.parseconfig() _request('patch', ISSUE.format(issueid=number, **cfg), data=json.dumps(data), params=params)
def add(params={}): comment = 'Available assignees: {0}\nAvailable labels: {1}'.format(u', '.join(labels()), u', '.join(assignees())) template = ticket.template(('title', 'assignee', 'labels', 'milestone_id', 'body'), comment=comment) val = util.inputwitheditor(template) if val == template: return data = _issuedata_from_template(val) cfg = config.parseconfig() r = _request('post', ISSUES.format(**cfg), data=json.dumps(data), params=params) return r
def comments(number, params={}): cfg = config.parseconfig() cj = _request('get', ISSUE_COMMENTS.format(issueid=number, **cfg), {'limit':50}) cj = [x for x in cj if x['content'] is not None] # commentは特殊。statusの変更がコメント化され、Web上では表示できるが、APIからは補足できない。 comments = [ticket.Comment(number = x['comment_id'], body = x['content'], creator = nested_access(x, 'author_info.username'), created = _todatetime(x['utc_created_on']), updated = _todatetime(x['utc_updated_on'])) for x in cj] return comments
def _request(rtype, url, params={}, data=None, headers={}): cfg = config.parseconfig() r = None # params['key'] = cfg['rtoken'] auth = (cfg['rtoken'] or cfg['name'], cfg['rpassword'] or 'password') if data: r = getattr(requests, rtype)(url, data=data, params=params, headers=headers, auth=auth, verify=cfg['sslverify']) else: r = getattr(requests, rtype)(url, params=params, headers=headers, auth=auth, verify=cfg['sslverify']) if not 200 <= r.status_code < 300: raise requests.exceptions.HTTPError('[{0}] {1}'.format(r.status_code, r.url)) return r.json
def _request(rtype, url, params={}, data=None, headers={}): cfg = config.parseconfig() r = None # params['key'] = cfg['rtoken'] auth = (cfg['rtoken'] or cfg['name'], cfg['rpassword'] or 'password') if data: r = getattr(requests, rtype)(url, data=data, params=params, headers=headers, auth=auth, verify=cfg['sslverify']) else: r = getattr(requests, rtype)(url, params=params, headers=headers, auth=auth, verify=cfg['sslverify']) if not 200 <= r.status_code < 300: raise requests.exceptions.HTTPError('[{0}] {1}'.format(r.status_code, r.url)) return r.json()
def comments(number, params={}): cfg = config.parseconfig() r = _request('get', ISSUE_COMMENTS.format(issueid=number, **cfg), params=params) comments = [ ticket.Comment(number=x['id'], body=x['body'], creator=nested_access(x, 'user.login'), created=todatetime(x['created_at']), updated=todatetime(x['updated_at'])) for x in r ] return comments
def issues(params={}): cfg = config.parseconfig() if 'limit' not in params: params['limit'] = 50 params['project_id'] = cfg['repo'] params['status_id'] = params.pop('state', '*') avail_state = ('open', 'closed', '*') if params['status_id'] not in avail_state: raise ValueError(u'Invarid query: available status are ({0})'.format(u', '.join(avail_state))) params['sort'] = params.pop('order', 'updated_on:desc') r = _request('get', ISSUES.format(**cfg), params=params) tickets = [_toticket(x) for x in r['issues']] return tickets
def changestate(number, state): avail_state = statuses() if state not in avail_state: if state == u'closed': if u'終了' in avail_state: state = u'終了' elif u'close' in avail_state: state = u'close' else: raise ValueError(u'Invarid query: available status are ({0})'.format(u', '.join(avail_state)).encode('utf-8')) data = {'issue':{'status_id': avail_state[state]}} cfg = config.parseconfig() _request('put', ISSUE.format(issueid=number, **cfg), data=json.dumps(data), headers={'content-type': 'application/json'})
def _request(rtype, url, params={}, data=None): cfg = config.parseconfig() if 'gtoken' in cfg: params['access_token'] = cfg['gtoken'] r = None if data: r = getattr(requests, rtype)(url, data=data, params=params, verify=cfg['sslverify']) else: r = getattr(requests, rtype)(url, params=params, verify=cfg['sslverify']) if not 200 <= r.status_code < 300: raise requests.exceptions.HTTPError('[{0}] {1}'.format(r.status_code, r.url)) if 'message' in r.json(): raise ValueError('Invarid query: {0}'.format(r['message'])) return r.json()
def show_config(opts): cfg = config.parseconfig(doverify=False) print('service: {c[service_name]}\n' 'username: {c[name]}\n' 'repository: {c[repo]}\n' 'sslverify: {c[sslverify]}').format(c=cfg) if cfg.get('service_name', '') == 'github': print 'github_token: {0}'.format(cfg['gtoken']) elif cfg.get('service_name', '') == 'bitbucket': print 'bitbucket_token: {0}'.format(cfg['btoken']) print 'bitbucket_token_secret: {0}'.format(cfg['btoken_secret']) elif cfg.get('service_name', '') == 'redmine': print 'redmine_endpoint: {0}'.format(cfg['rurl']) print 'redmine_token: {0}'.format(cfg['rtoken'])
def show_config(opts): cfg = config.parseconfig(doverify=False) print ('service: {c[service_name]}\n' 'username: {c[name]}\n' 'repository: {c[repo]}\n' 'sslverify: {c[sslverify]}').format(c=cfg) if cfg.get('service_name', '') == 'github': print 'github_token: {0}'.format(cfg['gtoken']) elif cfg.get('service_name', '') == 'bitbucket': print 'bitbucket_token: {0}'.format(cfg['btoken']) print 'bitbucket_token_secret: {0}'.format(cfg['btoken_secret']) elif cfg.get('service_name', '') == 'redmine': print 'redmine_endpoint: {0}'.format(cfg['rurl']) print 'redmine_token: {0}'.format(cfg['rtoken'])
def changestate(number, state): avail_state = statuses() state_map = {y:x for x, y in avail_state.iteritems()} if state not in state_map: if state == u'closed': if u'終了' in state_map: state = u'終了' elif u'close' in state_map: state = u'close' else: raise ValueError(u'Invarid query: available status are ({0})'.format(u', '.join(state_map)).encode('utf-8')) data = {'issue':{'status_id': state_map[state]}} cfg = config.parseconfig() _request('put', ISSUE.format(issueid=number, **cfg), data=json.dumps(data), headers={'content-type': 'application/json'})
def show(opts): cfg = config.parseconfig() r = cfg['service'].issue(opts['number']) ticket, comments = None, None # rがtupleの時は(ticket, comments) if isinstance(r, tuple): ticket, comments = r else: ticket = r print ticket.format(cfg['format_show'] or ticket._show_format) if not opts.get('nocomment', False): if comments is None: comments = cfg['service'].comments(opts['number']) for comment in comments: print comment.format(cfg['format_comment'])
def _request(rtype, url, params={}, data=None): cfg = config.parseconfig() session = requests if cfg['btoken'] and cfg['btoken_secret']: oauth = OAuth1Hook(CONSUMER_KEY, CONSUMER_SECRET, access_token=cfg['btoken'], access_token_secret=cfg['btoken_secret']) session = requests.session(hooks={'pre_request': oauth}) r = None if data: r = getattr(session, rtype)(url, data=data, params=params, verify=cfg['sslverify']) else: r = getattr(session, rtype)(url, params=params) if not 200 <= r.status_code < 300: raise requests.exceptions.HTTPError('[{0}] {1}'.format(r.status_code, r.url)) return r.json
def add(params={}): comment = 'Available assignees: {0}\nAvailable labels: {1}'.format( u', '.join(labels()), u', '.join(assignees())) template = ticket.template( ('title', 'assignee', 'labels', 'milestone_id', 'body'), comment=comment) val = util.inputwitheditor(template) if val == template: return data = _issuedata_from_template(val) cfg = config.parseconfig() r = _request('post', ISSUES.format(**cfg), data=json.dumps(data), params=params) return r
def test_parseconfig_success(monkeypatch): monkeypatch.setattr(config, 'git', mock_git) cfg = config.parseconfig() assert cfg['name'] == 'user' assert cfg['repo'] == 'testrepo' assert cfg['service_name'] == 'github' import gitticket.github assert cfg['service'] is gitticket.github assert cfg['sslverify'] == True assert cfg['format_list'] == 'list_format' assert cfg['format_show'] == 'show_format' assert cfg['format_comment'] == 'comment_format' assert cfg['gtoken'] == 'github_token' assert cfg['btoken'] == 'bitbucket_token' assert cfg['btoken_secret'] == 'bitbucket_token_secret' assert cfg['rurl'] == 'http://example.com' assert cfg['rtoken'] == 'redmine_token'
def _request(rtype, url, params={}, data=None): cfg = config.parseconfig() session = requests if cfg['btoken'] and cfg['btoken_secret']: service = OAuth1Service(name='bitbucket', consumer_key=CONSUMER_KEY, consumer_secret=CONSUMER_SECRET, request_token_url=OAUTH_REQUEST, access_token_url=OAUTH_ACCESS, authorize_url=OAUTH_AUTH) session = service.get_auth_session(cfg['btoken'], cfg['btoken_secret']) r = None if data: r = getattr(session, rtype)(url, data=data, params=params, verify=cfg['sslverify']) else: r = getattr(session, rtype)(url, params=params) if not 200 <= r.status_code < 300: raise requests.exceptions.HTTPError('[{0}] {1}'.format(r.status_code, r.url)) return r.json()
def update(number, params={}): tic = issue(number, params) comment = 'Available assignees: {0}\nAvailable labels: {1}'.format( u', '.join(labels()), u', '.join(assignees())) template = ticket.template( ('title', 'state', 'assignee', 'labels', 'milestone_id', 'body'), tic, comment=comment) val = util.inputwitheditor(template) if val == template: return data = _issuedata_from_template(val) cfg = config.parseconfig() _request('patch', ISSUE.format(issueid=number, **cfg), data=json.dumps(data), params=params)
def issues(params={}): cfg = config.parseconfig() url = ISSUES.format(**cfg) if 'limit' not in params: params['limit'] = 50 if 'state' in params: avail_states = ('new', 'open', 'resolved', 'on hold', 'invalid', 'duplicate', 'wontfix') if params['state'] not in avail_states: raise ValueError('Invarid query: available state are ({0})'.format(u', '.join(avail_states))) params['status'] = params.pop('state') if 'assignee' in params: params['responsible'] = params.pop('assignee') params['sort'] = params.pop('order', 'utc_last_updated') if params['sort'] == 'updated': params['sort'] = 'utc_last_updated' r = _request('get', url, params=params) tickets = [_toticket(x) for x in r['issues']] return tickets
def locals(opts): # チケット番号を見つける # id-xx, id/xx, idxx, #xxに対応 def find_ticket_number(s): mo = re.search(r'(?:id[/-]|#)(\d+)', s) if mo: return int(mo.group(1)) return None cfg = config.parseconfig() branches = config.git_branches() parsed_branches = filter(lambda x: x[0] is not None, ((find_ticket_number(x), x) for x in branches)) for issue_number, branch_name in parsed_branches: try: r = cfg['service'].issue(issue_number) except requests.exceptions.HTTPError: continue ticket = r[0] if isinstance(r, tuple) else r print u'({0})'.format(branch_name), ticket.format(cfg['format_list'])
def _toticket(d): cfg = config.parseconfig() j = dict(number = d['local_id'], state = d['status'], title = d['title'], body = d['content'], labels = nested_access(d, 'metadata.kind'), priority = d['priority'], milestone = nested_access(d, 'metadata.milestone'), creator = nested_access(d, 'reported_by.username'), creator_fullname = u' '.join((nested_access(d, 'reported_by.first_name'), nested_access(d, 'reported_by.last_name'))), html_url = ISSUEURL.format(issueid=d['local_id'], **cfg), assignee = nested_access(d, 'responsible.username'), comments = d['comment_count'], created = _todatetime(d['utc_created_on']), updated = _todatetime(d['utc_last_updated'])) if 'responsible' in d: j['assignee_fullname'] = u' '.join((nested_access(d, 'responsible.first_name'), nested_access(d, 'responsible.last_name'))) return ticket.Ticket(**j)
def _toticket(d): cfg = config.parseconfig() j = dict(number = d['id'], state = nested_access(d, 'status.name'), priority = nested_access(d, 'priority.name'), labels = nested_access(d, 'tracker.name'), html_url = ISSUE_URL.format(issueid=d['id'], **cfg), title = d['subject'], body = d.get('description', None), creator = user(nested_access(d, 'author.id'))['login'], creator_fullname = nested_access(d, 'author.name'), assignee_fullname = nested_access(d, 'assigned_to.name'), created = _todatetime(d['created_on']), updated = _todatetime(d['updated_on'])) if 'assigned_to' in d: j['assignee'] = user(nested_access(d, 'assigned_to.id'))['login'] tic = ticket.Ticket(**j) setattr(tic, 'priority_id', nested_access(d, 'priority.id')) # Redmineにpriorityを取得するAPIがないためあとで参照しなければならない return tic
def add(params={}): assigneedic = assignees() trackerdic = trackers() statusdic = statuses() comment = u'\n'.join((u'Available assignees: {0}', u'Available labels: {1}', u'Available priorities: 3-7 (low to high)', u'Available states: {2}') ).format(u', '.join(x['login'] for x in assigneedic.itervalues()), u', '.join(trackerdic.itervalues()), u', '.join(statusdic.itervalues())) template = ticket.template(('title', 'assignee', 'labels', 'priority', 'state', 'body'), comment=comment) val = util.inputwitheditor(template) if val == template: return data = _issuedata_from_template(val) cfg = config.parseconfig() data['issue']['project_id'] = cfg['repo'] r = _request('post', ISSUES.format(**cfg), data=json.dumps(data), params=params, headers={'content-type': 'application/json'})['issue'] return {'number':r['id'], 'html_url':ISSUE_URL.format(issueid=r['id'], **cfg)}
def update(number, params={}): assigneedic = assignees() trackerdic = trackers() statusdic = statuses() comment = u'\n'.join((u'Available assignees: {0}', u'Available labels: {1}', u'Available priorities: 3-7 (low to high)', u'Available states: {2}') ).format(u', '.join(x['login'] for x in assigneedic.itervalues()), u', '.join(trackerdic.itervalues()), u', '.join(statusdic.itervalues())) tic, _ = issue(number, params) tic.priority = tic.priority_id # FIXME: monkey patch template = ticket.template(('title', 'assignee', 'labels', 'priority', 'state', 'body', 'notes'), tic, comment=comment) val = util.inputwitheditor(template) if val == template: return data = _issuedata_from_template(val) cfg = config.parseconfig() _request('put', ISSUE.format(issueid=number, **cfg), data=json.dumps(data), params=params, headers={'content-type': 'application/json'})
def issue(number, params={}): cfg = config.parseconfig() r = _request('get', ISSUE.format(issueid=number, **cfg), params=params) return _toticket(r)