def show(globs: AttrDict, full: bool, patch: bool, patch_only: bool, browse: bool, bugs: List[int]): """Displaying bugs.""" results = [] tmpl = template.get_template('view', '/issue.txt') for bug_no in bugs: if browse: click.launch('https://github.com/{}/issues/{:d}'.format( globs.project, bug_no)) continue r, bug = globs.req_get(bug_no, model='Issue') if full and bug.comments: r, comments = globs.req_get('{}/comments'.format(bug_no), model='Comment') else: comments = [] if (patch or patch_only) and bug.pull_request: url = '{}/repos/{}/pulls/{}'.format(globs.host_url, globs.project, bug_no) headers = {'Accept': 'application/vnd.github.patch'} r, c = globs.req_get(url, headers=headers, is_json=False) patch = c.decode('utf-8') else: patch = None results.append(tmpl.render(bug=bug, comments=comments, full=True, patch=patch, patch_only=patch_only, project=globs.repo_obj())) if results: utils.pager('\n'.join(results), pager=globs.pager)
def list_bugs(globs: AttrDict, label: List[str], page: int, pull_requests: bool, order: str, state: str): """Listing bugs.""" bugs = [] params = {} if pull_requests: # FIXME: Dirty solution to supporting PRs only, needs rethink url = '{}/repos/{}/pulls'.format(globs.host_url, globs.project) else: url = '' if page != 1: params['page'] = page if label: params['labels'] = ','.join(label) states = ['open', 'closed'] if state == 'all' else [state, ] for state in states: _params = params.copy() _params['state'] = state r, _bugs = globs.req_get(url, params=_params, model='Issue') bugs.extend(_bugs) result = template.display_bugs(bugs, order, state=state, project=globs.repo_obj()) if result: utils.pager(result, pager=globs.pager)
def milestones(globs: AttrDict, order: str, state: str, create: str, list: bool): """Repository milestones.""" if not list and not create: fail('No action specified!') return 1 milestones_url = '{}/repos/{}/milestones'.format(globs.host_url, globs.project) r, milestones = globs.req_get(milestones_url, model='Milestone') if list: tmpl = template.get_template('view', '/list_milestones.txt') columns = utils.T.width if utils.T.width else 80 max_id = max(i.number for i in milestones) id_len = len(str(max_id)) result = tmpl.render(milestones=milestones, order=order, state=state, project=globs.repo_obj(), id_len=id_len, max_title=columns - id_len - 2) if result: utils.pager(result, pager=globs.pager) elif create: data = {'title': create} r, milestone = globs.req_post('', body=data, model='Milestone') success('Milestone {:d} created'.format(milestone.number))
def comment(globs: AttrDict, message: str, stdin: bool, bugs: List[int]): """Commenting on bugs.""" if stdin: message = click.get_text_stream().read() elif message: message = message else: message = template.edit_text() for bug in bugs: globs.req_post('{}/comments'.format(bug), body={'body': message}, model='Comment')
class InvalidKeyTest(TestCase): def setUp(self): self.ad = AttrDict(carrots=3, snacks=0) def test_invalid_key_set(self): with expect.raises(AttributeError): self.ad.__setattr__({True: False}, None) def test_invalid_key_delete(self): with expect.raises(AttributeError): self.ad.__delattr__({True: False})
def reopen(globs: AttrDict, stdin: bool, message: str, bugs: List[int]): """Reopening closed bugs.""" if stdin: message = click.get_text_stream().read() elif not message: try: message = template.edit_text() except template.EmptyMessageError: # Message isn't required for reopening, but it is good practice message = None for bug in bugs: if message: globs.req_post('{}/comments'.format(bug), body={'body': message}, model='Comment') globs.req_post(bug, body={'state': 'open'}, model='Issue')
def milestone(globs: AttrDict, milestone: str, bugs: List[int]): """Issue milestones.""" milestones_url = '{}/repos/{}/milestones'.format(globs.host_url, globs.project) r, milestones = globs.req_get(milestones_url, model='Milestone') milestone_mapping = dict((m.title, m.number) for m in milestones) try: milestone = milestone_mapping[milestone] except KeyError: raise ValueError('No such milestone {:!r}'.format(milestone)) for bug_no in bugs: globs.req_post(bug_no, body={'milestone': milestone}, model='Milestone')
def munge(dct: Dict) -> AttrDict: dct = AttrDict(**dct) if 'start' in dct: dct.start = parse_datetime(dct.start) if 'end' in dct: dct.end = parse_datetime(dct.end) return dct
def setup(globs: AttrDict, local: bool): """Setup GitHub access token.""" if not utils.SYSTEM_CERTS: warn('Falling back on bundled certificates') if utils.CURL_CERTS: warn('Using certs specified in $CURL_CERTS') default_user = os.getenv('GITHUB_USER', utils.get_git_config_val('github.user', getpass.getuser())) user = click.prompt('GitHub user', default_user) if not user: user = default_user password = click.prompt('GitHub password', hide_input=True, confirmation_prompt=False) private = click.confirm('Support private repositories') data = { 'scopes': ['repo' if private else 'public_repo'], 'note': 'hubugs', 'note_url': 'https://github.com/JNRowe/hubugs' } # Unfortunately, we need to forcibly define the header to workaround # GitHub sending a 404(in an effort to stop information leakage) header = { 'Authorization': 'Basic ' + b64encode(':'.join([user, password])) } r, auth = globs.req_post('https://api.github.com/authorizations', body=data, headers=header, model='Authorisation', token=False) utils.set_git_config_val('hubugs.token', auth.token, local) success('Configuration complete!')
def search(globs: AttrDict, order: str, state: str, term: str): """Searching bugs.""" search_url = '{}/search/issues'.format(globs.host_url) states = ['open', 'closed'] if state == 'all' else [state, ] params = { 'q': term, 'repo': globs.project, } bugs = [] for state in states: params['state'] = state r, c = globs.req_get(search_url, params=params, model='issue') bugs.extend(c.issues) result = template.display_bugs(bugs, order, term=term, state=state, project=globs.repo_obj()) if result: utils.pager(result, pager=globs.pager)
def label(globs: AttrDict, add: List[str], create: List[str], remove: List[str], list: bool, bugs: List[int]): """Labelling bugs.""" label_names = utils.sync_labels(globs, add, create) if list: click.echo(', '.join(sorted(label_names))) return for bug_no in bugs: r, bug = globs.req_get(bug_no, model='Issue') labels = [label.name for label in bug.labels] labels.extend(add + create) for string in remove: labels.remove(string) globs.req_post(bug_no, body={'labels': labels}, model='Label')
def edit(globs: AttrDict, stdin: bool, title: str, body:str, bugs: List[int]): """Editing bugs.""" if (title or stdin) and len(bugs) > 1: raise ValueError('Can not use --stdin or command line title/body ' 'with multiple bugs') for bug in bugs: if stdin: text = click.get_text_stream().readlines() elif not title: r, current = globs.req_get(bug, model='Issue') current_data = {'title': current.title, 'body': current.body} text = template.edit_text('open', current_data).splitlines() if stdin or not title: title = text[0] body = '\n'.join(text[1:]) else: title = title body = body data = {'title': title, 'body': body} globs.req_post(bug, body=data, model='Issue')
def report_bug(globs: AttrDict): """Report a new bug against hubugs.""" local = globs.project == 'JNRowe/hubugs' globs.project = 'JNRowe/hubugs' import html2text, jinja2, pygments # NOQA: E401 versions = dict([(m.__name__, getattr(m, '__version__', 'No version info')) for m in (click, html2text, httplib2, jinja2, pygments)]) data = { 'local': local, 'sys': sys, 'version': _version.dotted, 'versions': versions, 'certs': utils.CA_CERTS, } text = template.edit_text('hubugs_report', data).splitlines() title = text[0] body = '\n'.join(text[1:]) data = {'title': title, 'body': body} r, bug = globs.req_post('', body=data, model='Issue') success('Bug {:d} opened against hubugs, thanks!'.format(bug.number))
def munge(dct: Dict[str, str]) -> AttrDict: dct = AttrDict(**dct) if 'text' in dct: dct.text = html.escape(dct.text) for pat, repl in HTML_FILTERS.items(): dct.text = pat.sub(repl, dct.text) for pat, repl in ABBREVS.items(): dct.text = pat.sub(repl, dct.text) dct.text = dct.text.replace('\N{STX}', '<').replace('\N{ETX}', '>') if 'timestamp' in dct: dct.timestamp = parse_rfc3339(dct.timestamp) if 'self' in dct: dct.self = parse_rfc3339(dct.self) return dct
def open_bug(globs: AttrDict, add: List[str], create: List[str], stdin: bool, title: str, body: str): """Opening new bugs.""" utils.sync_labels(globs, add, create) if stdin: text = click.get_text_stream('stdin').readlines() elif not title: text = template.edit_text('open').splitlines() if stdin or not title: title = text[0] body = '\n'.join(text[1:]) else: title = title body = body data = {'title': title, 'body': body, 'labels': add + create} r, bug = globs.req_post('', body=data, model='Issue') success('Bug {:d} opened'.format(bug.number))
with open('data/µnotes.json') as f: notes = json.load(f, object_hook=AttrDict) with open('data/config.json') as f: config = json.load(f, object_hook=AttrDict) feed = AttrDict( version='https://jsonfeed.org/version/1.1', title='James Rowe', icon='https://micro.blog/JNRowe/avatar.jpg', home_page_url='https://jnrowe.github.com/mnotes/', feed_url='https://jnrowe.github.com/mnotes/feed.json', description=config.subtitle, authors=[ { 'name': config.author.name, 'url': config.author.uri, 'avatar': 'https://jnrowe.github.com/mnotes/avatar.png', }, ], language='en', items=[] ) for note, post in list(zip(reversed(notes), page.getroot().cssselect('.note')))[:15]: loc = '%s#TS%s' % (config.url, note.timestamp) content = html.tostring(post, True).decode() content = content.strip().replace('\n', '') item = AttrDict(
def setUp(self): self.ad = AttrDict(carrots=3, snacks=0)