bugwarrior-1.5.1/0000755000175000017500000000000013111575230015613 5ustar threebeanthreebean00000000000000bugwarrior-1.5.1/setup.cfg0000644000175000017500000000007313111575230017434 0ustar threebeanthreebean00000000000000[egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 bugwarrior-1.5.1/MANIFEST.in0000644000175000017500000000024012543264507017357 0ustar threebeanthreebean00000000000000include bugwarrior/README.rst include README.rst include LICENSE.txt recursive-include docs * recursive-include bugwarrior/docs *.rst recursive-include tests * bugwarrior-1.5.1/bugwarrior.egg-info/0000755000175000017500000000000013111575230021470 5ustar threebeanthreebean00000000000000bugwarrior-1.5.1/bugwarrior.egg-info/dependency_links.txt0000664000175000017500000000000113111575226025545 0ustar threebeanthreebean00000000000000 bugwarrior-1.5.1/bugwarrior.egg-info/requires.txt0000664000175000017500000000050713111575226024101 0ustar threebeanthreebean00000000000000requests taskw >= 0.8 python-dateutil pytz six>=1.9.0 jinja2>=2.7.2 dogpile.cache>=0.5.3 lockfile>=0.9.1 click future!=0.16.0 [activecollab] pypandoc pyac>=0.1.5 [bts] PySimpleSOAP python-debianbts>=2.6.1 [bugzilla] python-bugzilla [jira] jira>=0.22 [keyring] keyring dbus-python [megaplan] megaplan>=1.4 [trac] offtrac bugwarrior-1.5.1/bugwarrior.egg-info/entry_points.txt0000664000175000017500000000236213111575226025000 0ustar threebeanthreebean00000000000000 [console_scripts] bugwarrior-pull = bugwarrior:pull bugwarrior-vault = bugwarrior:vault bugwarrior-uda = bugwarrior:uda [bugwarrior.service] github=bugwarrior.services.github:GithubService gitlab=bugwarrior.services.gitlab:GitlabService bitbucket=bugwarrior.services.bitbucket:BitbucketService trac=bugwarrior.services.trac:TracService bts=bugwarrior.services.bts:BTSService bugzilla=bugwarrior.services.bz:BugzillaService teamlab=bugwarrior.services.teamlab:TeamLabService redmine=bugwarrior.services.redmine:RedMineService activecollab2=bugwarrior.services.activecollab2:ActiveCollab2Service activecollab=bugwarrior.services.activecollab:ActiveCollabService jira=bugwarrior.services.jira:JiraService megaplan=bugwarrior.services.megaplan:MegaplanService phabricator=bugwarrior.services.phab:PhabricatorService versionone=bugwarrior.services.versionone:VersionOneService pagure=bugwarrior.services.pagure:PagureService taiga=bugwarrior.services.taiga:TaigaService gerrit=bugwarrior.services.gerrit:GerritService trello=bugwarrior.services.trello:TrelloService youtrack=bugwarrior.services.youtrack:YoutrackService bugwarrior-1.5.1/bugwarrior.egg-info/SOURCES.txt0000664000175000017500000001201713111575230023357 0ustar threebeanthreebean00000000000000LICENSE.txt MANIFEST.in README.rst setup.py bugwarrior/README.rst bugwarrior/__init__.py bugwarrior/command.py bugwarrior/config.py bugwarrior/data.py bugwarrior/db.py bugwarrior/notifications.py bugwarrior.egg-info/PKG-INFO bugwarrior.egg-info/SOURCES.txt bugwarrior.egg-info/dependency_links.txt bugwarrior.egg-info/entry_points.txt bugwarrior.egg-info/not-zip-safe bugwarrior.egg-info/requires.txt bugwarrior.egg-info/top_level.txt bugwarrior/docs/common_configuration.rst bugwarrior/docs/configuration.rst bugwarrior/docs/contributing.rst bugwarrior/docs/faq.rst bugwarrior/docs/getting.rst bugwarrior/docs/index.rst bugwarrior/docs/services.rst bugwarrior/docs/using.rst bugwarrior/docs/services/activecollab.rst bugwarrior/docs/services/activecollab2.rst bugwarrior/docs/services/bitbucket.rst bugwarrior/docs/services/bts.rst bugwarrior/docs/services/bugzilla.rst bugwarrior/docs/services/gerrit.rst bugwarrior/docs/services/github.rst bugwarrior/docs/services/gitlab.rst bugwarrior/docs/services/jira.rst bugwarrior/docs/services/megaplan.rst bugwarrior/docs/services/pagure.rst bugwarrior/docs/services/phabricator.rst bugwarrior/docs/services/redmine.rst bugwarrior/docs/services/taiga.rst bugwarrior/docs/services/teamlab.rst bugwarrior/docs/services/trac.rst bugwarrior/docs/services/trello.rst bugwarrior/docs/services/versionone.rst bugwarrior/docs/services/youtrack.rst bugwarrior/services/__init__.py bugwarrior/services/activecollab.py bugwarrior/services/activecollab2.py bugwarrior/services/bitbucket.py bugwarrior/services/bts.py bugwarrior/services/bz.py bugwarrior/services/gerrit.py bugwarrior/services/github.py bugwarrior/services/gitlab.py bugwarrior/services/jira.py bugwarrior/services/mplan.py bugwarrior/services/pagure.py bugwarrior/services/phab.py bugwarrior/services/redmine.py bugwarrior/services/taiga.py bugwarrior/services/teamlab.py bugwarrior/services/trac.py bugwarrior/services/trello.py bugwarrior/services/versionone.py bugwarrior/services/youtrack.py tests/__init__.py tests/__init__.pyc tests/base.py tests/base.pyc tests/test_activecollab.py tests/test_activecollab.pyc tests/test_activecollab2.py tests/test_activecollab2.pyc tests/test_bitbucket.py tests/test_bitbucket.pyc tests/test_bts.py tests/test_bts.pyc tests/test_bugzilla.py tests/test_bugzilla.pyc tests/test_config.py tests/test_config.pyc tests/test_data.py tests/test_data.pyc tests/test_db.py tests/test_db.pyc tests/test_gerrit.py tests/test_gerrit.pyc tests/test_github.py tests/test_github.pyc tests/test_gitlab.py tests/test_gitlab.pyc tests/test_jira.py tests/test_jira.pyc tests/test_megaplan.py tests/test_megaplan.pyc tests/test_redmine.py tests/test_redmine.pyc tests/test_service.py tests/test_service.pyc tests/test_string_compat.py tests/test_string_compat.pyc tests/test_taiga.py tests/test_taiga.pyc tests/test_teamlab.py tests/test_teamlab.pyc tests/test_templates.py tests/test_templates.pyc tests/test_trac.py tests/test_trac.pyc tests/test_trello.py tests/test_trello.pyc tests/test_youtrak.py tests/test_youtrak.pyc tests/__pycache__/__init__.cpython-34.pyc tests/__pycache__/__init__.cpython-35.pyc tests/__pycache__/base.cpython-34.pyc tests/__pycache__/base.cpython-35.pyc tests/__pycache__/test_activecollab.cpython-34.pyc tests/__pycache__/test_activecollab.cpython-35.pyc tests/__pycache__/test_activecollab2.cpython-34.pyc tests/__pycache__/test_activecollab2.cpython-35.pyc tests/__pycache__/test_bitbucket.cpython-34.pyc tests/__pycache__/test_bitbucket.cpython-35.pyc tests/__pycache__/test_bts.cpython-34.pyc tests/__pycache__/test_bts.cpython-35.pyc tests/__pycache__/test_bugzilla.cpython-34.pyc tests/__pycache__/test_bugzilla.cpython-35.pyc tests/__pycache__/test_config.cpython-34.pyc tests/__pycache__/test_config.cpython-35.pyc tests/__pycache__/test_data.cpython-34.pyc tests/__pycache__/test_data.cpython-35.pyc tests/__pycache__/test_db.cpython-34.pyc tests/__pycache__/test_db.cpython-35.pyc tests/__pycache__/test_gerrit.cpython-34.pyc tests/__pycache__/test_gerrit.cpython-35.pyc tests/__pycache__/test_github.cpython-34.pyc tests/__pycache__/test_github.cpython-35.pyc tests/__pycache__/test_gitlab.cpython-34.pyc tests/__pycache__/test_gitlab.cpython-35.pyc tests/__pycache__/test_jira.cpython-34.pyc tests/__pycache__/test_jira.cpython-35.pyc tests/__pycache__/test_megaplan.cpython-34.pyc tests/__pycache__/test_megaplan.cpython-35.pyc tests/__pycache__/test_redmine.cpython-34.pyc tests/__pycache__/test_redmine.cpython-35.pyc tests/__pycache__/test_service.cpython-35.pyc tests/__pycache__/test_string_compat.cpython-34.pyc tests/__pycache__/test_string_compat.cpython-35.pyc tests/__pycache__/test_taiga.cpython-34.pyc tests/__pycache__/test_taiga.cpython-35.pyc tests/__pycache__/test_teamlab.cpython-34.pyc tests/__pycache__/test_teamlab.cpython-35.pyc tests/__pycache__/test_templates.cpython-34.pyc tests/__pycache__/test_templates.cpython-35.pyc tests/__pycache__/test_trac.cpython-34.pyc tests/__pycache__/test_trac.cpython-35.pyc tests/__pycache__/test_trello.cpython-34.pyc tests/__pycache__/test_trello.cpython-35.pyc tests/__pycache__/test_youtrak.cpython-35.pycbugwarrior-1.5.1/bugwarrior.egg-info/not-zip-safe0000664000175000017500000000000112733611166023730 0ustar threebeanthreebean00000000000000 bugwarrior-1.5.1/bugwarrior.egg-info/top_level.txt0000664000175000017500000000001313111575226024223 0ustar threebeanthreebean00000000000000bugwarrior bugwarrior-1.5.1/bugwarrior.egg-info/PKG-INFO0000664000175000017500000000663313111575226022604 0ustar threebeanthreebean00000000000000Metadata-Version: 1.1 Name: bugwarrior Version: 1.5.1 Summary: Sync github, bitbucket, and trac issues with taskwarrior Home-page: http://github.com/ralphbean/bugwarrior Author: Ralph Bean Author-email: ralph.bean@gmail.com License: GPLv3+ Description: ``bugwarrior`` is a command line utility for updating your local `taskwarrior `_ database from your forge issue trackers. It currently supports the following remote resources: - `github `_ (api v3) - `gitlab `_ (api v3) - `bitbucket `_ - `pagure `_ - `bugzilla `_ - `trac `_ - `megaplan `_ - `teamlab `_ - `redmine `_ - `jira `_ - `taiga `_ - `gerrit `_ - `activecollab `_ (2.x and 4.x) - `phabricator `_ - `versionone `_ - `trello `_ - `youtrack `_ Documentation ------------- For information on how to install and use bugwarrior, read `the docs `_ on RTFD. Build Status ------------ .. |master| image:: https://secure.travis-ci.org/ralphbean/bugwarrior.png?branch=master :alt: Build Status - master branch :target: https://travis-ci.org/#!/ralphbean/bugwarrior .. |develop| image:: https://secure.travis-ci.org/ralphbean/bugwarrior.png?branch=develop :alt: Build Status - develop branch :target: https://travis-ci.org/#!/ralphbean/bugwarrior +----------+-----------+ | Branch | Status | +==========+===========+ | master | |master| | +----------+-----------+ | develop | |develop| | +----------+-----------+ Contributors ------------ - Ralph Bean (primary author) - Ben Boeckel (contributed support for Gitlab) - Justin Forest (contributed support for RedMine, TeamLab, and MegaPlan, as well as some unicode help) - Tycho Garen (contributed support for Jira) - Kosta Harlan (contributed support for activeCollab, notifications, and experimental taskw support) - Luke Macken (contributed some code cleaning) - James Rowe (contributed to the docs) - Adam Coddington (anti-entropy crusader) - Iain R. Learmonth (contributed support for the Debian BTS and maintains the Debian package) - BinaryBabel (contributed support for YouTrack) Keywords: task taskwarrior todo github Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Bug Tracking Classifier: Topic :: Utilities bugwarrior-1.5.1/PKG-INFO0000644000175000017500000000663313111575230016720 0ustar threebeanthreebean00000000000000Metadata-Version: 1.1 Name: bugwarrior Version: 1.5.1 Summary: Sync github, bitbucket, and trac issues with taskwarrior Home-page: http://github.com/ralphbean/bugwarrior Author: Ralph Bean Author-email: ralph.bean@gmail.com License: GPLv3+ Description: ``bugwarrior`` is a command line utility for updating your local `taskwarrior `_ database from your forge issue trackers. It currently supports the following remote resources: - `github `_ (api v3) - `gitlab `_ (api v3) - `bitbucket `_ - `pagure `_ - `bugzilla `_ - `trac `_ - `megaplan `_ - `teamlab `_ - `redmine `_ - `jira `_ - `taiga `_ - `gerrit `_ - `activecollab `_ (2.x and 4.x) - `phabricator `_ - `versionone `_ - `trello `_ - `youtrack `_ Documentation ------------- For information on how to install and use bugwarrior, read `the docs `_ on RTFD. Build Status ------------ .. |master| image:: https://secure.travis-ci.org/ralphbean/bugwarrior.png?branch=master :alt: Build Status - master branch :target: https://travis-ci.org/#!/ralphbean/bugwarrior .. |develop| image:: https://secure.travis-ci.org/ralphbean/bugwarrior.png?branch=develop :alt: Build Status - develop branch :target: https://travis-ci.org/#!/ralphbean/bugwarrior +----------+-----------+ | Branch | Status | +==========+===========+ | master | |master| | +----------+-----------+ | develop | |develop| | +----------+-----------+ Contributors ------------ - Ralph Bean (primary author) - Ben Boeckel (contributed support for Gitlab) - Justin Forest (contributed support for RedMine, TeamLab, and MegaPlan, as well as some unicode help) - Tycho Garen (contributed support for Jira) - Kosta Harlan (contributed support for activeCollab, notifications, and experimental taskw support) - Luke Macken (contributed some code cleaning) - James Rowe (contributed to the docs) - Adam Coddington (anti-entropy crusader) - Iain R. Learmonth (contributed support for the Debian BTS and maintains the Debian package) - BinaryBabel (contributed support for YouTrack) Keywords: task taskwarrior todo github Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: GNU General Public License (GPL) Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Topic :: Software Development :: Bug Tracking Classifier: Topic :: Utilities bugwarrior-1.5.1/bugwarrior/0000755000175000017500000000000013111575230017776 5ustar threebeanthreebean00000000000000bugwarrior-1.5.1/bugwarrior/services/0000755000175000017500000000000013111575230021621 5ustar threebeanthreebean00000000000000bugwarrior-1.5.1/bugwarrior/services/phab.py0000644000175000017500000001510513111574702023112 0ustar threebeanthreebean00000000000000from builtins import str import six from bugwarrior.config import aslist from bugwarrior.services import IssueService, Issue # This comes from PyPI import phabricator import logging log = logging.getLogger(__name__) class PhabricatorIssue(Issue): TITLE = 'phabricatortitle' URL = 'phabricatorurl' TYPE = 'phabricatortype' OBJECT_NAME = 'phabricatorid' UDAS = { TITLE: { 'type': 'string', 'label': 'Phabricator Title', }, URL: { 'type': 'string', 'label': 'Phabricator URL', }, TYPE: { 'type': 'string', 'label': 'Phabricator Type', }, OBJECT_NAME: { 'type': 'string', 'label': 'Phabricator Object', }, } UNIQUE_KEY = (URL, ) def to_taskwarrior(self): return { 'project': self.extra['project'], 'priority': self.origin['default_priority'], 'annotations': self.extra.get('annotations', []), self.URL: self.record['uri'], self.TYPE: self.extra['type'], self.TITLE: self.record['title'], self.OBJECT_NAME: self.record['uri'].split('/')[-1], } def get_default_description(self): return self.build_default_description( title=self.record['title'], url=self.get_processed_url(self.record['uri']), number=self.record['uri'].split('/')[-1], cls=self.extra['type'], ) class PhabricatorService(IssueService): ISSUE_CLASS = PhabricatorIssue CONFIG_PREFIX = 'phabricator' def __init__(self, *args, **kw): super(PhabricatorService, self).__init__(*args, **kw) # These reads in login credentials from ~/.arcrc self.api = phabricator.Phabricator() self.shown_user_phids = ( self.config.get("user_phids", None, aslist)) self.shown_project_phids = ( self.config.get("project_phids", None, aslist)) def issues(self): # TODO -- get a list of these from the api projects = {} # If self.shown_user_phids or self.shown_project_phids is set, retrict API calls to user_phids or project_phids # to avoid time out with Phabricator installations with huge userbase if (self.shown_user_phids is not None) or (self.shown_project_phids is not None): if self.shown_user_phids is not None: issues_owner = self.api.maniphest.query(status='status-open', ownerPHIDs=self.shown_user_phids) issues_cc = self.api.maniphest.query(status='status-open', ccPHIDs=self.shown_user_phids) issues_author = self.api.maniphest.query(status='status-open', authorPHIDs=self.shown_user_phids) issues = list(issues_owner.items()) + list(issues_cc.items()) + list(issues_author.items()) # Delete duplicates seen = set() issues = [item for item in issues if str(item[1]) not in seen and not seen.add(str(item[1]))] if self.shown_project_phids is not None: issues = self.api.maniphest.query(status='status-open', projectsPHIDs = self.shown_project_phids) issues = issues.items() else: issues = self.api.maniphest.query(status='status-open') issues = issues.items() log.info("Found %i issues" % len(issues)) for phid, issue in issues: project = self.target # a sensible default try: project = projects.get(issue['projectPHIDs'][0], project) except IndexError: pass this_issue_matches = False if self.shown_user_phids is None and self.shown_project_phids is None: this_issue_matches = True if self.shown_user_phids is not None: # Checking whether authorPHID, ccPHIDs, ownerPHID # are intersecting with self.shown_user_phids issue_relevant_to = set(issue['ccPHIDs'] + [issue['ownerPHID'], issue['authorPHID']]) if len(issue_relevant_to.intersection(self.shown_user_phids)) > 0: this_issue_matches = True if self.shown_project_phids is not None: # Checking whether projectPHIDs # is intersecting with self.shown_project_phids issue_relevant_to = set(issue['projectPHIDs']) if len(issue_relevant_to.intersection(self.shown_user_phids)) > 0: this_issue_matches = True if not this_issue_matches: continue extra = { 'project': project, 'type': 'issue', #'annotations': self.annotations(phid, issue) } yield self.get_issue_for_record(issue, extra) diffs = self.api.differential.query(status='status-open') diffs = list(diffs) log.info("Found %i differentials" % len(diffs)) for diff in diffs: project = self.target # a sensible default try: project = projects.get(issue['projectPHIDs'][0], project) except IndexError: pass this_diff_matches = False if self.shown_user_phids is None and self.shown_project_phids is None: this_diff_matches = True if self.shown_user_phids is not None: # Checking whether authorPHID, ccPHIDs, ownerPHID # are intersecting with self.shown_user_phids diff_relevant_to = set(list(diff['reviewers']) + [diff['authorPHID']]) if len(diff_relevant_to.intersection(self.shown_user_phids)) > 0: this_diff_matches = True if self.shown_project_phids is not None: # Checking whether projectPHIDs # is intersecting with self.shown_project_phids phabricator_projects = [] try: phabricator_projects = diff['phabricator:projects'] except KeyError: pass diff_relevant_to = set(phabricator_projects + [diff['repositoryPHID']]) if len(diff_relevant_to.intersection(self.shown_user_phids)) > 0: this_diff_matches = True if not this_diff_matches: continue extra = { 'project': project, 'type': 'pull_request', #'annotations': self.annotations(phid, issue) } yield self.get_issue_for_record(diff, extra) bugwarrior-1.5.1/bugwarrior/services/trac.py0000644000175000017500000001351413111574702023133 0ustar threebeanthreebean00000000000000from future import standard_library standard_library.install_aliases() from builtins import filter from builtins import map from builtins import range import offtrac import csv import io as StringIO import requests import urllib.request, urllib.parse, urllib.error from bugwarrior.config import die, asbool from bugwarrior.services import Issue, IssueService import logging log = logging.getLogger(__name__) class TracIssue(Issue): SUMMARY = 'tracsummary' URL = 'tracurl' NUMBER = 'tracnumber' COMPONENT = 'traccomponent' UDAS = { SUMMARY: { 'type': 'string', 'label': 'Trac Summary', }, URL: { 'type': 'string', 'label': 'Trac URL', }, NUMBER: { 'type': 'numeric', 'label': 'Trac Number', }, COMPONENT: { 'type': 'string', 'label': 'Trac Component', }, } UNIQUE_KEY = (URL, ) PRIORITY_MAP = { 'trivial': 'L', 'minor': 'L', 'major': 'M', 'critical': 'H', 'blocker': 'H', } def to_taskwarrior(self): return { 'project': self.extra['project'], 'priority': self.get_priority(), 'annotations': self.extra['annotations'], self.URL: self.record['url'], self.SUMMARY: self.record['summary'], self.NUMBER: self.record['number'], self.COMPONENT: self.record['component'], } def get_default_description(self): if 'number' in self.record: number = self.record['number'] else: number = self.record['id'] return self.build_default_description( title=self.record['summary'], url=self.get_processed_url(self.record['url']), number=number, cls='issue' ) def get_priority(self): return self.PRIORITY_MAP.get( self.record.get('priority'), self.origin['default_priority'] ) class TracService(IssueService): ISSUE_CLASS = TracIssue CONFIG_PREFIX = 'trac' def __init__(self, *args, **kw): super(TracService, self).__init__(*args, **kw) base_uri = self.config.get('base_uri') scheme = self.config.get('scheme', default='https') username = self.config.get('username', default=None) if username: password = self.get_password('password', username) auth = urllib.parse.quote_plus('%s:%s' % (username, password)) + '@' else: auth = '' if self.config.get('no_xmlrpc', default=False, to_type=asbool): uri = '%s://%s%s/' % (scheme, auth, base_uri) self.uri = uri self.trac = None else: uri = '%s://%s%s/login/xmlrpc' % (scheme, auth, base_uri) self.trac = offtrac.TracServer(uri) @staticmethod def get_keyring_service(service_config): username = service_config.get('username') base_uri = service_config.get('base_uri') return "https://%s@%s/" % (username, base_uri) @classmethod def validate_config(cls, service_config, target): if 'base_uri' not in service_config: die("[%s] has no 'base_uri'" % target) elif '://' in service_config.get('base_uri'): die("[%s] do not include scheme in 'base_uri'" % target) IssueService.validate_config(service_config, target) def annotations(self, tag, issue, issue_obj): annotations = [] # without offtrac, we can't get issue comments if self.trac is None: return annotations changelog = self.trac.server.ticket.changeLog(issue['number']) for time, author, field, oldvalue, newvalue, permament in changelog: if field == 'comment': annotations.append((author, newvalue, )) url = issue_obj.get_processed_url(issue['url']) return self.build_annotations(annotations, url) def get_owner(self, issue): tag, issue = issue return issue.get('owner', None) or None def issues(self): base_url = "https://" + self.config.get('base_uri') if self.trac: tickets = self.trac.query_tickets('status!=closed&max=0') tickets = list(map(self.trac.get_ticket, tickets)) issues = [(self.target, ticket[3]) for ticket in tickets] for i in range(len(issues)): issues[i][1]['url'] = "%s/ticket/%i" % (base_url, tickets[i][0]) issues[i][1]['number'] = tickets[i][0] else: resp = requests.get( self.uri + 'query', params={ 'status': '!closed', 'max': '0', 'format': 'csv', 'col': ['id', 'summary', 'owner', 'priority', 'component'], }) if resp.status_code != 200: raise RuntimeError("Trac responded with %s" % resp) # strip Trac's bogus BOM text = resp.text[1:].lstrip(u'\ufeff') tickets = list(csv.DictReader(StringIO.StringIO(text.encode('utf-8')))) issues = [(self.target, ticket) for ticket in tickets] for i in range(len(issues)): issues[i][1]['url'] = "%s/ticket/%s" % (base_url, tickets[i]['id']) issues[i][1]['number'] = int(tickets[i]['id']) log.debug(" Found %i total.", len(issues)) issues = list(filter(self.include, issues)) log.debug(" Pruned down to %i", len(issues)) for project, issue in issues: issue_obj = self.get_issue_for_record(issue) extra = { 'annotations': self.annotations(project, issue, issue_obj), 'project': project, } issue_obj.update_extra(extra) yield issue_obj bugwarrior-1.5.1/bugwarrior/services/gitlab.py0000644000175000017500000003773513111574702023457 0ustar threebeanthreebean00000000000000# coding: utf-8 from future import standard_library standard_library.install_aliases() from builtins import map from builtins import filter from configparser import NoOptionError import re import requests import six from jinja2 import Template from bugwarrior.config import asbool, aslist, die from bugwarrior.services import IssueService, Issue, ServiceClient import logging log = logging.getLogger(__name__) class GitlabIssue(Issue): TITLE = 'gitlabtitle' DESCRIPTION = 'gitlabdescription' CREATED_AT = 'gitlabcreatedon' UPDATED_AT = 'gitlabupdatedat' DUEDATE = 'gitlabduedate' MILESTONE = 'gitlabmilestone' URL = 'gitlaburl' REPO = 'gitlabrepo' TYPE = 'gitlabtype' NUMBER = 'gitlabnumber' STATE = 'gitlabstate' UPVOTES = 'gitlabupvotes' DOWNVOTES = 'gitlabdownvotes' WORK_IN_PROGRESS = 'gitlabwip' AUTHOR = 'gitlabauthor' ASSIGNEE = 'gitlabassignee' UDAS = { TITLE: { 'type': 'string', 'label': 'Gitlab Title', }, DESCRIPTION: { 'type': 'string', 'label': 'Gitlab Description', }, CREATED_AT: { 'type': 'date', 'label': 'Gitlab Created', }, UPDATED_AT: { 'type': 'date', 'label': 'Gitlab Updated', }, DUEDATE: { 'type': 'date', 'label': 'Gitlab Due Date', }, MILESTONE: { 'type': 'string', 'label': 'Gitlab Milestone', }, URL: { 'type': 'string', 'label': 'Gitlab URL', }, REPO: { 'type': 'string', 'label': 'Gitlab Repo Slug', }, TYPE: { 'type': 'string', 'label': 'Gitlab Type', }, NUMBER: { 'type': 'numeric', 'label': 'Gitlab Issue/MR #', }, STATE: { 'type': 'string', 'label': 'Gitlab Issue/MR State', }, UPVOTES: { 'type': 'numeric', 'label': 'Gitlab Upvotes', }, DOWNVOTES: { 'type': 'numeric', 'label': 'Gitlab Downvotes', }, WORK_IN_PROGRESS: { 'type': 'numeric', 'label': 'Gitlab MR Work-In-Progress Flag', }, AUTHOR: { 'type': 'string', 'label': 'Gitlab Author', }, ASSIGNEE: { 'type': 'string', 'label': 'Gitlab Assignee', }, } UNIQUE_KEY = (REPO, TYPE, NUMBER,) def _normalize_label_to_tag(self, label): return re.sub(r'[^a-zA-Z0-9]', '_', label) def to_taskwarrior(self): author = self.record['author'] milestone = self.record.get('milestone') created = self.record['created_at'] updated = self.record.get('updated_at') state = self.record['state'] upvotes = self.record.get('upvotes', 0) downvotes = self.record.get('downvotes', 0) work_in_progress = self.record.get('work_in_progress', 0) assignee = self.record.get('assignee') duedate = self.record.get('due_date') number = ( self.record['id'] if self.extra['type'] == 'todo' else self.record['iid']) priority = ( self.origin['default_priority'] if self.extra['type'] == 'issue' else 'H') title = ( 'Todo from %s for %s' % (author['name'], self.extra['project']) if self.extra['type'] == 'todo' else self.record['title']) description = ( self.record['body'] if self.extra['type'] == 'todo' else self.record['description']) if milestone and ( self.extra['type'] == 'issue' or (self.extra['type'] == 'merge_request' and duedate is None)): duedate = milestone['due_date'] if milestone: milestone = milestone['title'] if created: created = self.parse_date(created).replace(microsecond=0) if updated: updated = self.parse_date(updated).replace(microsecond=0) if duedate: duedate = self.parse_date(duedate) if author: author = author['username'] if assignee: assignee = assignee['username'] self.title = title return { 'project': self.extra['project'], 'priority': priority, 'annotations': self.extra.get('annotations', []), 'tags': self.get_tags(), self.URL: self.extra['issue_url'], self.REPO: self.extra['project'], self.TYPE: self.extra['type'], self.TITLE: title, self.DESCRIPTION: description, self.MILESTONE: milestone, self.NUMBER: number, self.CREATED_AT: created, self.UPDATED_AT: updated, self.DUEDATE: duedate, self.STATE: state, self.UPVOTES: upvotes, self.DOWNVOTES: downvotes, self.WORK_IN_PROGRESS: work_in_progress, self.AUTHOR: author, self.ASSIGNEE: assignee, } def get_tags(self): tags = [] if not self.origin['import_labels_as_tags']: return tags context = self.record.copy() label_template = Template(self.origin['label_template']) for label in self.record.get('labels', []): context.update({ 'label': self._normalize_label_to_tag(label) }) tags.append( label_template.render(context) ) return tags def get_default_description(self): return self.build_default_description( title=self.title, url=self.get_processed_url(self.extra['issue_url']), number=self.record.get('iid', ''), cls=self.extra['type'], ) class GitlabService(IssueService, ServiceClient): ISSUE_CLASS = GitlabIssue CONFIG_PREFIX = 'gitlab' def __init__(self, *args, **kw): super(GitlabService, self).__init__(*args, **kw) host = self.config.get( 'host', default='gitlab.com', to_type=six.text_type) self.login = self.config.get('login') token = self.get_password('token', self.login) self.auth = (host, token) if self.config.get('use_https', default=True, to_type=asbool): self.scheme = 'https' else: self.scheme = 'http' self.verify_ssl = self.config.get( 'verify_ssl', default=True, to_type=asbool ) self.exclude_repos = self.config.get('exclude_repos', [], aslist) self.include_repos = self.config.get('include_repos', [], aslist) self.include_repos = list(map(self.add_default_namespace, self.include_repos)) self.exclude_repos = list(map(self.add_default_namespace, self.exclude_repos)) self.import_labels_as_tags = self.config.get( 'import_labels_as_tags', default=False, to_type=asbool ) self.label_template = self.config.get( 'label_template', default='{{label}}', to_type=six.text_type ) self.filter_merge_requests = self.config.get( 'filter_merge_requests', default=False, to_type=asbool ) self.include_todos = self.config.get( 'include_todos', default=False, to_type=asbool ) self.include_all_todos = self.config.get( 'include_all_todos', default=True, to_type=asbool ) def add_default_namespace(self, repo): """ Add a default namespace to a repository name. If the name already contains a namespace, it will be returned unchanged: e.g. "foo/bar" → "foo/bar" otherwise, the loggin will be prepended as namespace: e.g. "bar" → "/bar" """ if repo.find('/') < 0: return self.login + '/' + repo else: return repo @staticmethod def get_keyring_service(service_config): login = service_config.get('login') host = service_config.get('host', default='gitlab.com') return "gitlab://%s@%s" % (login, host) def get_service_metadata(self): return { 'import_labels_as_tags': self.import_labels_as_tags, 'label_template': self.label_template, } def get_owner(self, issue): if issue[1]['assignee'] != None and issue[1]['assignee']['username']: return issue[1]['assignee']['username'] def get_author(self, issue): if issue[1]['author'] != None and issue[1]['author']['username']: return issue[1]['author']['username'] def filter_repos(self, repo): if self.exclude_repos: if repo['path_with_namespace'] in self.exclude_repos: return False if self.include_repos: if repo['path_with_namespace'] in self.include_repos: return True else: return False return True def _get_notes(self, rid, issue_type, issueid): tmpl = '{scheme}://{host}/api/v3/projects/%d/%s/%d/notes' % (rid, issue_type, issueid) return self._fetch_paged(tmpl) def annotations(self, repo, url, issue_type, issue, issue_obj): notes = self._get_notes(repo['id'], issue_type, issue['id']) return self.build_annotations( (( n['author']['username'], n['body'] ) for n in notes), issue_obj.get_processed_url(url) ) def _fetch(self, tmpl, **kwargs): url = tmpl.format(scheme=self.scheme, host=self.auth[0]) headers = {'PRIVATE-TOKEN': self.auth[1]} if not self.verify_ssl: requests.packages.urllib3.disable_warnings() response = requests.get(url, headers=headers, verify=self.verify_ssl, **kwargs) return self.json_response(response) def _fetch_paged(self, tmpl): params = { 'page': 1, 'per_page': 100, } full = [] detect_broken_gitlab_pagination = [] while True: items = self._fetch(tmpl, params=params) if not items: break # XXX: Some gitlab versions have a bug where pagination doesn't # work and instead return the entire result no matter what. Detect # this by seeing if the results are the same as the last time # around and bail if so. Unfortunately, while it is a GitLab bug, # we have to deal with instances where it exists. if items == detect_broken_gitlab_pagination: break detect_broken_gitlab_pagination = items full += items if len(items) < params['per_page']: break params['page'] += 1 return full def get_repo_issues(self, rid): tmpl = '{scheme}://{host}/api/v3/projects/%d/issues' % rid issues = {} try: repo_issues = self._fetch_paged(tmpl) except IOError: # Projects may have issues disabled. return {} for issue in repo_issues: if issue['state'] not in ('opened', 'reopened'): continue issues[issue['id']] = (rid, issue) return issues def get_repo_merge_requests(self, rid): tmpl = '{scheme}://{host}/api/v3/projects/%d/merge_requests' % rid issues = {} try: repo_merge_requests = self._fetch_paged(tmpl) except IOError: # Projects may have merge requests disabled. return {} for issue in repo_merge_requests: if issue['state'] not in ('opened', 'reopened'): continue issues[issue['id']] = (rid, issue) return issues def get_todos(self): tmpl = '{scheme}://{host}/api/v3/todos' todos = [] try: fetched_todos = self._fetch_paged(tmpl) except IOError: # Older gitlab versions do not have todo items. return {} for todo in fetched_todos: if todo['state'] == 'done': continue todos.append((todo.get('project'), todo)) return todos def include_todo(self, repos): ids = list(r['id'] for r in repos) def include_todo(todo): project, todo = todo return project is None or project['id'] in ids return include_todo def issues(self): tmpl = '{scheme}://{host}/api/v3/projects' all_repos = self._fetch_paged(tmpl) repos = list(filter(self.filter_repos, all_repos)) repo_map = {} issues = {} for repo in repos: rid = repo['id'] repo_map[rid] = repo issues.update( self.get_repo_issues(rid) ) log.debug(" Found %i issues.", len(issues)) issues = list(filter(self.include, issues.values())) log.debug(" Pruned down to %i issues.", len(issues)) for rid, issue in issues: repo = repo_map[rid] issue['repo'] = repo['path'] issue_obj = self.get_issue_for_record(issue) issue_url = '%s/issues/%d' % (repo['web_url'], issue['iid']) extra = { 'issue_url': issue_url, 'project': repo['path'], 'type': 'issue', 'annotations': self.annotations(repo, issue_url, 'issues', issue, issue_obj) } issue_obj.update_extra(extra) yield issue_obj if not self.filter_merge_requests: merge_requests = {} for repo in repos: rid = repo['id'] merge_requests.update( self.get_repo_merge_requests(rid) ) log.debug(" Found %i merge requests.", len(merge_requests)) merge_requests = list(filter(self.include, merge_requests.values())) log.debug(" Pruned down to %i merge requests.", len(merge_requests)) for rid, issue in merge_requests: repo = repo_map[rid] issue['repo'] = repo['path'] issue_obj = self.get_issue_for_record(issue) issue_url = '%s/merge_requests/%d' % (repo['web_url'], issue['iid']) extra = { 'issue_url': issue_url, 'project': repo['path'], 'type': 'merge_request', 'annotations': self.annotations(repo, issue_url, 'merge_requests', issue, issue_obj) } issue_obj.update_extra(extra) yield issue_obj if self.include_todos: todos = self.get_todos() log.debug(" Found %i todo items.", len(todos)) if not self.include_all_todos: todos = list(filter(self.include_todo(repos), todos)) log.debug(" Pruned down to %i todos.", len(todos)) for project, todo in todos: if project is not None: repo = project else: repo = { 'path': 'the instance', } todo['repo'] = repo['path'] todo_obj = self.get_issue_for_record(todo) todo_url = todo['target_url'] extra = { 'issue_url': todo_url, 'project': repo['path'], 'type': 'todo', 'annotations': [], } todo_obj.update_extra(extra) yield todo_obj @classmethod def validate_config(cls, service_config, target): if 'host' not in service_config: die("[%s] has no 'gitlab.host'" % target) if 'login' not in service_config: die("[%s] has no 'gitlab.login'" % target) if 'token' not in service_config: die("[%s] has no 'gitlab.token'" % target) super(GitlabService, cls).validate_config(service_config, target) bugwarrior-1.5.1/bugwarrior/services/bitbucket.py0000644000175000017500000002113013111574702024147 0ustar threebeanthreebean00000000000000from __future__ import unicode_literals from builtins import filter import requests from bugwarrior.services import IssueService, Issue, ServiceClient from bugwarrior.config import asbool, aslist, die import logging log = logging.getLogger(__name__) class BitbucketIssue(Issue): TITLE = 'bitbuckettitle' URL = 'bitbucketurl' FOREIGN_ID = 'bitbucketid' UDAS = { TITLE: { 'type': 'string', 'label': 'Bitbucket Title', }, URL: { 'type': 'string', 'label': 'Bitbucket URL', }, FOREIGN_ID: { 'type': 'numeric', 'label': 'Bitbucket Issue ID', } } UNIQUE_KEY = (URL, ) PRIORITY_MAP = { 'trivial': 'L', 'minor': 'L', 'major': 'M', 'critical': 'H', 'blocker': 'H', } def to_taskwarrior(self): return { 'project': self.extra['project'], 'priority': self.get_priority(), 'annotations': self.extra['annotations'], self.URL: self.extra['url'], self.FOREIGN_ID: self.record['id'], self.TITLE: self.record['title'], } def get_default_description(self): return self.build_default_description( title=self.record['title'], url=self.get_processed_url(self.extra['url']), number=self.record['id'], cls='issue' ) class BitbucketService(IssueService, ServiceClient): ISSUE_CLASS = BitbucketIssue CONFIG_PREFIX = 'bitbucket' BASE_API = 'https://api.bitbucket.org/1.0' BASE_API2 = 'https://api.bitbucket.org/2.0' BASE_URL = 'https://bitbucket.org/' def __init__(self, *args, **kw): super(BitbucketService, self).__init__(*args, **kw) key = self.config.get('key') secret = self.config.get('secret') auth = {'oauth': (key, secret)} refresh_token = self.config.data.get('bitbucket_refresh_token') if not refresh_token: login = self.config.get('login') password = self.get_password('password', login) auth['basic'] = (login, password) if key and secret: if refresh_token: response = requests.post( self.BASE_URL + 'site/oauth2/access_token', data={'grant_type': 'refresh_token', 'refresh_token': refresh_token}, auth=auth['oauth']).json() else: response = requests.post( self.BASE_URL + 'site/oauth2/access_token', data={'grant_type': 'password', 'username': login, 'password': password}, auth=auth['oauth']).json() self.config.data.set('bitbucket_refresh_token', response['refresh_token']) auth['token'] = response['access_token'] self.requests_kwargs = {} if 'token' in auth: self.requests_kwargs['headers'] = { 'Authorization': 'Bearer ' + auth['token']} elif 'basic' in auth: self.requests_kwargs['auth'] = auth['basic'] self.exclude_repos = self.config.get('exclude_repos', [], aslist) self.include_repos = self.config.get('include_repos', [], aslist) self.filter_merge_requests = self.config.get( 'filter_merge_requests', default=False, to_type=asbool ) @staticmethod def get_keyring_service(service_config): login = service_config.get('login') username = service_config.get('username') return "bitbucket://%s@bitbucket.org/%s" % (login, username) def filter_repos(self, repo_tag): repo = repo_tag.split('/').pop() if self.exclude_repos: if repo in self.exclude_repos: return False if self.include_repos: if repo in self.include_repos: return True else: return False return True def get_data(self, url): """ Perform a request to the fully qualified url and return json. """ return self.json_response(requests.get(url, **self.requests_kwargs)) def get_collection(self, url): """ Pages through an object collection from the bitbucket API. Returns an iterator that lazily goes through all the 'values' of all the pages in the collection. """ url = self.BASE_API2 + url while url is not None: response = self.get_data(url) for value in response['values']: yield value url = response.get('next', None) @classmethod def validate_config(cls, service_config, target): if 'username' not in service_config: die("[%s] has no 'username'" % target) if 'login' not in service_config: die("[%s] has no 'login'" % target) IssueService.validate_config(service_config, target) def fetch_issues(self, tag): response = self.get_collection('/repositories/%s/issues/' % (tag)) return [(tag, issue) for issue in response] def fetch_pull_requests(self, tag): response = self.get_collection('/repositories/%s/pullrequests/' % tag) return [(tag, issue) for issue in response] def get_annotations(self, tag, issue, issue_obj, url): response = self.get_data( self.BASE_API + '/repositories/%s/issues/%i/comments' % (tag, issue['id'])) return self.build_annotations( (( comment['author_info']['username'], comment['content'], ) for comment in response), issue_obj.get_processed_url(url) ) def get_annotations2(self, tag, issue, issue_obj, url): response = self.get_collection( '/repositories/%s/pullrequests/%i/comments' % (tag, issue['id']) ) return self.build_annotations( (( comment['user']['username'], comment['content']['raw'], ) for comment in response), issue_obj.get_processed_url(url) ) def get_owner(self, issue): _, issue = issue assignee = issue.get('assignee', None) if assignee is not None: return assignee.get('username', None) def issues(self): user = self.config.get('username') response = self.get_collection('/repositories/' + user + '/') repo_tags = list(filter(self.filter_repos, [ repo['full_name'] for repo in response if repo.get('has_issues') ])) issues = sum([self.fetch_issues(repo) for repo in repo_tags], []) log.debug(" Found %i total.", len(issues)) closed = ['resolved', 'duplicate', 'wontfix', 'invalid', 'closed'] try: issues = [tup for tup in issues if tup[1]['status'] not in closed] except KeyError: # Undocumented API change. issues = [tup for tup in issues if tup[1]['state'] not in closed] issues = list(filter(self.include, issues)) log.debug(" Pruned down to %i", len(issues)) for tag, issue in issues: issue_obj = self.get_issue_for_record(issue) url = issue['links']['html']['href'] extras = { 'project': tag.split('/')[1], 'url': url, 'annotations': self.get_annotations(tag, issue, issue_obj, url) } issue_obj.update_extra(extras) yield issue_obj if not self.filter_merge_requests: pull_requests = sum( [self.fetch_pull_requests(repo) for repo in repo_tags], []) log.debug(" Found %i total.", len(pull_requests)) closed = ['rejected', 'fulfilled'] not_resolved = lambda tup: tup[1]['state'] not in closed pull_requests = list(filter(not_resolved, pull_requests)) pull_requests = list(filter(self.include, pull_requests)) log.debug(" Pruned down to %i", len(pull_requests)) for tag, issue in pull_requests: issue_obj = self.get_issue_for_record(issue) url = self.BASE_URL + '/'.join( issue['links']['html']['href'].split('/')[3:] ).replace('pullrequests', 'pullrequest') extras = { 'project': tag.split('/')[1], 'url': url, 'annotations': self.get_annotations2(tag, issue, issue_obj, url) } issue_obj.update_extra(extras) yield issue_obj bugwarrior-1.5.1/bugwarrior/services/taiga.py0000644000175000017500000001013213111574702023260 0ustar threebeanthreebean00000000000000from __future__ import absolute_import import requests import six from bugwarrior.db import CACHE_REGION as cache from bugwarrior.config import die from bugwarrior.services import IssueService, Issue, ServiceClient class TaigaIssue(Issue): SUMMARY = 'taigasummary' URL = 'taigaurl' FOREIGN_ID = 'taigaid' UDAS = { SUMMARY: { 'type': 'string', 'label': 'Taiga Summary' }, URL: { 'type': 'string', 'label': 'Taiga URL', }, FOREIGN_ID: { 'type': 'numeric', 'label': 'Taiga Issue ID' }, } UNIQUE_KEY = (URL, ) def to_taskwarrior(self): return { 'project': self.extra['project'], 'annotations': self.extra['annotations'], self.URL: self.extra['url'], 'priority': self.origin['default_priority'], 'tags': self.record['tags'], self.FOREIGN_ID: self.record['ref'], self.SUMMARY: self.record['subject'], } def get_default_description(self): return self.build_default_description( title=self.record['subject'], url=self.get_processed_url(self.extra['url']), number=self.record['ref'], cls='issue', ) class TaigaService(IssueService, ServiceClient): ISSUE_CLASS = TaigaIssue CONFIG_PREFIX = 'taiga' def __init__(self, *args, **kw): super(TaigaService, self).__init__(*args, **kw) self.url = self.config.get('base_uri') self.auth_token = self.get_password('auth_token') self.label_template = self.config.get( 'label_template', default='{{label}}', to_type=six.text_type ) self.session = requests.session() self.session.headers.update({ 'Accept': 'application/json', 'Authorization': 'Bearer %s' % self.auth_token, }) @staticmethod def get_keyring_service(service_config): base_uri = service_config.get('base_uri') return "taiga://%s" % base_uri def get_service_metadata(self): return { 'url': self.url, 'label_template': self.label_template, } @classmethod def validate_config(cls, service_config, target): for option in ('auth_token', 'base_uri'): if option not in service_config: die("[%s] has no 'taiga.%s'" % (target, option)) IssueService.validate_config(service_config, target) def issues(self): url = self.url + '/api/v1/users/me' me = self.session.get(url) data = me.json() # Check for errors and bail if we failed. if '_error_message' in data: raise RuntimeError("{_error_type} {_error_message}".format(**data)) # Otherwise, proceed. userid = data['id'] url = self.url + '/api/v1/userstories' params = dict(assigned_to=userid, status__is_closed="false") response = self.session.get(url, params=params) stories = response.json() for story in stories: project = self.get_project(story['project']) extra = { 'project': project['slug'], 'annotations': self.annotations(story, project), 'url': self.build_url(story, project), } yield self.get_issue_for_record(story, extra) @cache.cache_on_arguments() def get_project(self, project_id): url = '%s/api/v1/projects/%i' % (self.url, project_id) return self.json_response(self.session.get(url)) def build_url(self, story, project): return '%s/project/%s/us/%i' % (self.url, project['slug'], story['ref']) def annotations(self, story, project): url = '%s/api/v1/history/userstory/%i' % (self.url, story['id']) response = self.session.get(url) history = response.json() return self.build_annotations( (( item['user']['username'], item['comment'], ) for item in history if item['comment']), self.build_url(story, project) ) bugwarrior-1.5.1/bugwarrior/services/mplan.py0000644000175000017500000000575413111574702023320 0ustar threebeanthreebean00000000000000from __future__ import absolute_import import megaplan from bugwarrior.config import die from bugwarrior.services import IssueService, Issue import logging log = logging.getLogger(__name__) class MegaplanIssue(Issue): URL = 'megaplanurl' FOREIGN_ID = 'megaplanid' TITLE = 'megaplantitle' UDAS = { TITLE: { 'type': 'string', 'label': 'Megaplan Title', }, URL: { 'type': 'string', 'label': 'Megaplan URL', }, FOREIGN_ID: { 'type': 'string', 'label': 'Megaplan Issue ID' } } UNIQUE_KEY = (URL, ) def to_taskwarrior(self): return { 'project': self.get_project(), 'priority': self.get_priority(), self.FOREIGN_ID: self.record['Id'], self.URL: self.get_issue_url(), self.TITLE: self.get_issue_title(), } def get_project(self): return self.origin['project_name'] def get_default_description(self): return self.build_default_description( title=self.get_issue_title(), url=self.get_processed_url(self.get_issue_url()), number=self.record['Id'], cls='issue', ) def get_issue_url(self): return "https://%s/task/%d/card/" % ( self.origin['hostname'], self.record["Id"] ) def get_issue_title(self): parts = self.record["Name"].split("|") return parts[-1].strip() def get_issue_id(self): if self.record["Id"] > 1000000: return self.record["Id"] - 1000000 return self.record["Id"] class MegaplanService(IssueService): ISSUE_CLASS = MegaplanIssue CONFIG_PREFIX = 'megaplan' def __init__(self, *args, **kw): super(MegaplanService, self).__init__(*args, **kw) self.hostname = self.config.get('hostname') _login = self.config.get('login') _password = self.get_password('password', _login) self.client = megaplan.Client(self.hostname) self.client.authenticate(_login, _password) self.project_name = self.config.get('project_name', self.hostname) @staticmethod def get_keyring_service(service_config): login = service_config.get('login') hostname = service_config.get('hostname') return "megaplan://%s@%s" % (login, hostname) def get_service_metadata(self): return { 'project_name': self.project_name, 'hostname': self.hostname, } @classmethod def validate_config(cls, service_config, target): for k in ('login', 'password', 'hostname'): if k not in service_config: die("[%s] has no 'mplan.%s'" % (target, k)) IssueService.validate_config(service_config, target) def issues(self): issues = self.client.get_actual_tasks() log.debug(" Found %i total.", len(issues)) for issue in issues: yield self.get_issue_for_record(issue) bugwarrior-1.5.1/bugwarrior/services/bz.py0000644000175000017500000002243713111574702022621 0ustar threebeanthreebean00000000000000import bugzilla import time import pytz import datetime import six from bugwarrior.config import die, asbool from bugwarrior.services import IssueService, Issue import logging log = logging.getLogger(__name__) class BugzillaIssue(Issue): URL = 'bugzillaurl' SUMMARY = 'bugzillasummary' BUG_ID = 'bugzillabugid' STATUS = 'bugzillastatus' NEEDINFO = 'bugzillaneedinfo' PRODUCT = 'bugzillaproduct' COMPONENT = 'bugzillacomponent' UDAS = { URL: { 'type': 'string', 'label': 'Bugzilla URL', }, SUMMARY: { 'type': 'string', 'label': 'Bugzilla Summary', }, STATUS: { 'type': 'string', 'label': 'Bugzilla Status', }, BUG_ID: { 'type': 'numeric', 'label': 'Bugzilla Bug ID', }, NEEDINFO: { 'type': 'date', 'label': 'Bugzilla Needinfo', }, PRODUCT: { 'type': 'string', 'label': 'Bugzilla Product', }, COMPONENT: { 'type': 'string', 'label': 'Bugzilla Component', }, } UNIQUE_KEY = (URL, ) PRIORITY_MAP = { 'unspecified': 'M', 'low': 'L', 'medium': 'M', 'high': 'H', 'urgent': 'H', } def to_taskwarrior(self): task = { 'project': self.record['component'], 'priority': self.get_priority(), 'annotations': self.extra.get('annotations', []), self.URL: self.extra['url'], self.SUMMARY: self.record['summary'], self.BUG_ID: self.record['id'], self.STATUS: self.record['status'], self.PRODUCT: self.record['product'], self.COMPONENT: self.record['component'], } if self.extra.get('needinfo_since', None) is not None: task[self.NEEDINFO] = self.extra.get('needinfo_since') return task def get_default_description(self): return self.build_default_description( title=self.record['summary'], url=self.get_processed_url(self.extra['url']), number=self.record['id'], cls='issue', ) _open_statuses = [ 'NEW', 'ASSIGNED', 'NEEDINFO', 'ON_DEV', 'MODIFIED', 'POST', 'REOPENED', 'ON_QA', 'FAILS_QA', 'PASSES_QA', ] class BugzillaService(IssueService): ISSUE_CLASS = BugzillaIssue CONFIG_PREFIX = 'bugzilla' COLUMN_LIST = [ 'id', 'status', 'summary', 'priority', 'product', 'component', 'flags', 'longdescs', ] def __init__(self, *args, **kw): super(BugzillaService, self).__init__(*args, **kw) self.base_uri = self.config.get('base_uri') self.username = self.config.get('username') self.ignore_cc = self.config.get('ignore_cc', default=False, to_type=lambda x: x == "True") self.query_url = self.config.get('query_url', default=None) self.include_needinfos = self.config.get( 'include_needinfos', False, to_type=lambda x: x == "True") self.open_statuses = self.config.get( 'open_statuses', _open_statuses, to_type=lambda x: x.split(',')) log.debug(" filtering on statuses: %r", self.open_statuses) # So more modern bugzilla's require that we specify # query_format=advanced along with the xmlrpc request. # https://bugzilla.redhat.com/show_bug.cgi?id=825370 # ...but older bugzilla's don't know anything about that argument. # Here we make it possible for the user to specify whether they want # to pass that argument or not. self.advanced = asbool(self.config.get('advanced', 'no')) url = 'https://%s/xmlrpc.cgi' % self.base_uri api_key = self.config.get('api_key', default=None) if api_key: try: self.bz = bugzilla.Bugzilla(url=url, api_key=api_key) except TypeError: raise Exception("Bugzilla API keys require python-bugzilla>=2.1.0") else: password = self.get_password('password', self.username) self.bz = bugzilla.Bugzilla(url=url) self.bz.login(self.username, password) @staticmethod def get_keyring_service(service_config): username = service_config.get('username') base_uri = service_config.get('base_uri') return "bugzilla://%s@%s" % (username, base_uri) @classmethod def validate_config(cls, service_config, target): req = ['username', 'base_uri'] for option in req: if option not in service_config: die("[%s] has no 'bugzilla.%s'" % (target, option)) if 'password' not in service_config and 'api_key' not in service_config: die("[%s] has neither 'bugzilla.password' nor 'bugzilla.api_key'" % (target,)) super(BugzillaService, cls).validate_config(service_config, target) def get_owner(self, issue): # NotImplemented, but we should never get called since .include() isn't # used by this IssueService. raise NotImplementedError def annotations(self, tag, issue, issue_obj): base_url = "https://%s/show_bug.cgi?id=" % self.base_uri long_url = base_url + six.text_type(issue['id']) url = issue_obj.get_processed_url(long_url) if 'comments' in issue: comments = issue.get('comments', []) return self.build_annotations( (( c['author'].split('@')[0], c['text'], ) for c in comments), url ) else: # Backwards compatibility (old python-bugzilla/bugzilla instances) # This block handles a million different contingencies that have to # do with different version of python-bugzilla and different # version of bugzilla itself. :( comments = issue.get('longdescs', []) def _parse_author(obj): if isinstance(obj, dict): return obj['login_name'].split('@')[0] else: return obj def _parse_body(obj): return obj.get('text', obj.get('body')) return self.build_annotations( (( _parse_author(c['author']), _parse_body(c) ) for c in comments), url ) def issues(self): email = self.username # TODO -- doing something with blockedby would be nice. if self.query_url: query = self.bz.url_to_query(self.query_url) query['column_list'] = self.COLUMN_LIST else: query = dict( column_list=self.COLUMN_LIST, bug_status=self.open_statuses, email1=email, emailreporter1=1, emailassigned_to1=1, emailqa_contact1=1, emailtype1="substring", ) if not self.ignore_cc: query['emailcc1'] = 1 if self.advanced: # Required for new bugzilla # https://bugzilla.redhat.com/show_bug.cgi?id=825370 query['query_format'] = 'advanced' bugs = self.bz.query(query) if self.include_needinfos: needinfos = self.bz.query(dict( column_list=self.COLUMN_LIST, quicksearch='flag:needinfo?%s' % email, )) exists = [b.id for b in bugs] for bug in needinfos: # don't double-add bugs that have already been found if bug.id in exists: continue bugs.append(bug) # Convert to dicts bugs = [ dict( ((col, _get_bug_attr(bug, col)) for col in self.COLUMN_LIST) ) for bug in bugs ] issues = [(self.target, bug) for bug in bugs] log.debug(" Found %i total.", len(issues)) # Build a url for each issue base_url = "https://%s/show_bug.cgi?id=" % (self.base_uri) for tag, issue in issues: issue_obj = self.get_issue_for_record(issue) extra = { 'url': base_url + six.text_type(issue['id']), 'annotations': self.annotations(tag, issue, issue_obj), } needinfos = [f for f in issue['flags'] if ( f['name'] == 'needinfo' and f['status'] == '?' and f.get('requestee', self.username) == self.username)] if needinfos: last_mod = needinfos[0]['modification_date'] # convert from RPC DateTime string to datetime.datetime object mod_date = datetime.datetime.fromtimestamp( time.mktime(last_mod.timetuple())) extra['needinfo_since'] = pytz.UTC.localize(mod_date) issue_obj.update_extra(extra) yield issue_obj def _get_bug_attr(bug, attr): """Default only the longdescs case to [] since it may not be present.""" if attr == "longdescs": return getattr(bug, attr, []) return getattr(bug, attr) bugwarrior-1.5.1/bugwarrior/services/trello.py0000644000175000017500000001652113111574702023504 0ustar threebeanthreebean00000000000000""" Trello service Pulls trello cards as tasks. Trello API documentation available at https://developers.trello.com/ """ from __future__ import unicode_literals from future import standard_library standard_library.install_aliases() from configparser import NoOptionError from jinja2 import Template import requests from bugwarrior.services import IssueService, Issue, ServiceClient from bugwarrior.config import die, asbool, aslist DEFAULT_LABEL_TEMPLATE = "{{label|replace(' ', '_')}}" class TrelloIssue(Issue): NAME = 'trellocard' CARDID = 'trellocardid' BOARD = 'trelloboard' LIST = 'trellolist' SHORTLINK = 'trelloshortlink' SHORTURL = 'trelloshorturl' URL = 'trellourl' UDAS = { NAME: {'type': 'string', 'label': 'Trello card name'}, CARDID: {'type': 'string', 'label': 'Trello card ID'}, BOARD: {'type': 'string', 'label': 'Trello board name'}, LIST: {'type': 'string', 'label': 'Trello list name'}, SHORTLINK: {'type': 'string', 'label': 'Trello shortlink'}, SHORTURL: {'type': 'string', 'label': 'Trello short URL'}, URL: {'type': 'string', 'label': 'Trello URL'}, } UNIQUE_KEY = (CARDID,) def get_default_description(self): """ Return the old-style verbose description from bugwarrior. """ return self.build_default_description( title=self.record['name'], url=self.record['shortUrl'], number=self.record['idShort'], cls='task', ) def get_tags(self, twdict): tmpl = Template( self.origin.get('label_template', DEFAULT_LABEL_TEMPLATE)) return [tmpl.render(twdict, label=label['name']) for label in self.record['labels']] def to_taskwarrior(self): twdict = { 'project': self.extra['boardname'], 'priority': 'M', self.NAME: self.record['name'], self.CARDID: self.record['id'], self.BOARD: self.extra['boardname'], self.LIST: self.extra['listname'], self.SHORTLINK: self.record['shortLink'], self.SHORTURL: self.record['shortUrl'], self.URL: self.record['url'], 'annotations': self.extra.get('annotations', []), } if self.origin['import_labels_as_tags']: twdict['tags'] = self.get_tags(twdict) return twdict class TrelloService(IssueService, ServiceClient): ISSUE_CLASS = TrelloIssue # What prefix should we use for this service's configuration values CONFIG_PREFIX = 'trello' @classmethod def validate_config(cls, service_config, target): def check_key(opt): """ Check that the given key exist in the configuration """ if opt not in service_config: die("[{}] has no 'trello.{}'".format(target, opt)) super(TrelloService, cls).validate_config(service_config, target) check_key('token') check_key('api_key') def get_service_metadata(self): """ Return extra config options to be passed to the TrelloIssue class """ return { 'import_labels_as_tags': self.config.get('import_labels_as_tags', False, asbool), 'label_template': self.config.get('label_template', DEFAULT_LABEL_TEMPLATE), } def issues(self): """ Returns a list of dicts representing issues from a remote service. """ for board in self.get_boards(): for lst in self.get_lists(board['id']): listextra = dict(boardname=board['name'], listname=lst['name']) for card in self.get_cards(lst['id']): issue = self.get_issue_for_record(card, extra=listextra) issue.update_extra({"annotations": self.annotations(card)}) yield issue def annotations(self, card_json): """ A wrapper around get_comments that build the taskwarrior annotations. """ comments = self.get_comments(card_json['id']) annotations = self.build_annotations( ((c['memberCreator']['username'], c['data']['text']) for c in comments), card_json["shortUrl"]) return annotations def get_boards(self): """ Get the list of boards to pull cards from. If the user gave a value to trello.include_boards use that, otherwise ask the Trello API for the user's boards. """ if 'include_boards' in self.config: for boardid in self.config.get('include_boards', to_type=aslist): # Get the board name yield self.api_request( "/1/boards/{id}".format(id=boardid), fields='name') else: boards = self.api_request("/1/members/me/boards", fields='name') for board in boards: yield board def get_lists(self, board): """ Returns a list of the filtered lists for the given board This filters the trello lists according to the configuration values of trello.include_lists and trello.exclude_lists. """ lists = self.api_request( "/1/boards/{board_id}/lists/open".format(board_id=board), fields='name') include_lists = self.config.get('include_lists', to_type=aslist) if include_lists: lists = [l for l in lists if l['name'] in include_lists] exclude_lists = self.config.get('exclude_lists', to_type=aslist) if exclude_lists: lists = [l for l in lists if l['name'] not in exclude_lists] return lists def get_cards(self, list_id): """ Returns an iterator for the cards in a given list, filtered according to configuration values of trello.only_if_assigned and trello.also_unassigned """ params = {'fields': 'name,idShort,shortLink,shortUrl,url,labels'} member = self.config.get('only_if_assigned', None) unassigned = self.config.get('also_unassigned', False, asbool) if member is not None: params['members'] = 'true' params['member_fields'] = 'username' cards = self.api_request( "/1/lists/{list_id}/cards/open".format(list_id=list_id), **params) for card in cards: if (member is None or member in [m['username'] for m in card['members']] or (unassigned and not card['members'])): yield card def get_comments(self, card_id): """ Returns an iterator for the comments on a certain card. """ params = {'filter': 'commentCard', 'memberCreator_fields': 'username'} comments = self.api_request( "/1/cards/{card_id}/actions".format(card_id=card_id), **params) for comment in comments: assert comment['type'] == 'commentCard' yield comment def api_request(self, url, **params): """ Make a trello API request. This takes an absolute url (without protocol and host) and a list of argumnets and return a GET request with the key and token from the configuration """ params['key'] = self.config.get('api_key'), params['token'] = self.config.get('token'), url = "https://api.trello.com" + url return self.json_response(requests.get(url, params=params)) bugwarrior-1.5.1/bugwarrior/services/bts.py0000644000175000017500000001522213111574702022770 0ustar threebeanthreebean00000000000000from builtins import str import debianbts import requests from bugwarrior.config import die, asbool from bugwarrior.services import Issue, IssueService, ServiceClient import logging log = logging.getLogger(__name__) UDD_BUGS_SEARCH = "https://udd.debian.org/bugs/" class BTSIssue(Issue): SUBJECT = 'btssubject' URL = 'btsurl' NUMBER = 'btsnumber' PACKAGE = 'btspackage' SOURCE = 'btssource' FORWARDED = 'btsforwarded' STATUS = 'btsstatus' UDAS = { SUBJECT: { 'type': 'string', 'label': 'Debian BTS Subject', }, URL: { 'type': 'string', 'label': 'Debian BTS URL', }, NUMBER: { 'type': 'numeric', 'label': 'Debian BTS Number', }, PACKAGE: { 'type': 'string', 'label': 'Debian BTS Package', }, SOURCE: { 'type': 'string', 'label': 'Debian BTS Source Package', }, FORWARDED: { 'type': 'string', 'label': 'Debian BTS Forwarded URL', }, STATUS: { 'type': 'string', 'label': 'Debian BTS Status', } } UNIQUE_KEY = (URL, ) PRIORITY_MAP = { 'wishlist': 'L', 'minor': 'L', 'normal': 'M', 'important': 'M', 'serious': 'H', 'grave': 'H', 'critical': 'H', } def to_taskwarrior(self): return { 'priority': self.get_priority(), self.URL: self.record['url'], self.SUBJECT: self.record['subject'], self.NUMBER: self.record['number'], self.PACKAGE: self.record['package'], self.SOURCE: self.record['source'], self.FORWARDED: self.record['forwarded'], self.STATUS: self.record['status'], } def get_default_description(self): return self.build_default_description( title=self.record['subject'], url=self.get_processed_url(self.record['url']), number=self.record['number'], cls='issue' ) def get_priority(self): return self.PRIORITY_MAP.get( self.record.get('severity'), self.origin['default_priority'] ) class BTSService(IssueService, ServiceClient): ISSUE_CLASS = BTSIssue CONFIG_PREFIX = 'bts' def __init__(self, *args, **kw): super(BTSService, self).__init__(*args, **kw) self.email = self.config.get('email', default=None) self.packages = self.config.get('packages', default=None) self.udd = self.config.get( 'udd', default=False, to_type=asbool) self.udd_ignore_sponsor = self.config.get( 'udd_ignore_sponsor', default=True, to_type=asbool) self.ignore_pkg = self.config.get('ignore_pkg', default=None) self.ignore_src = self.config.get('ignore_src', default=None) self.ignore_pending = self.config.get( 'ignore_pending', default=True, to_type=asbool) @classmethod def validate_config(cls, service_config, target): if ('udd' in service_config and asbool(service_config.get('udd')) and 'email' not in service_config): die("[%s] has no 'bts.email' but UDD search was requested" % (target,)) if 'packages' not in service_config and 'email' not in service_config: die("[%s] has neither 'bts.email' or 'bts.packages'" % (target,)) if ('udd_ignore_sponsor' in service_config and (not asbool(service_config.get('udd')))): die("[%s] defines settings for UDD search without enabling" " UDD search" % (target,)) IssueService.validate_config(service_config, target) def _record_for_bug(self, bug): return {'number': bug.bug_num, 'url': 'https://bugs.debian.org/' + str(bug.bug_num), 'package': bug.package, 'subject': bug.subject, 'severity': bug.severity, 'source': bug.source, 'forwarded': bug.forwarded, 'status': bug.pending, } def _get_udd_bugs(self): request_params = { 'format': 'json', 'dmd': 1, 'email1': self.email, } if self.udd_ignore_sponsor: request_params['nosponsor1'] = "on" resp = requests.get(UDD_BUGS_SEARCH, request_params) return self.json_response(resp) def issues(self): # Initialise empty list of bug numbers collected_bugs = [] # Search BTS for bugs owned by email address if self.email: owned_bugs = debianbts.get_bugs("owner", self.email, "status", "open") collected_bugs.extend(owned_bugs) # Search BTS for bugs related to specified packages if self.packages: packages = self.packages.split(",") for pkg in packages: pkg_bugs = debianbts.get_bugs("package", pkg, "status", "open") for bug in pkg_bugs: if bug not in collected_bugs: collected_bugs.append(bug) # Search UDD bugs search for bugs belonging to packages that # are maintained by the email address if self.udd: udd_bugs = self._get_udd_bugs() for bug in udd_bugs: if bug not in collected_bugs: collected_bugs.append(bug['id']) issues = [self._record_for_bug(bug) for bug in debianbts.get_status(collected_bugs)] log.debug(" Found %i total.", len(issues)) if self.ignore_pkg: ignore_pkg = self.ignore_pkg.split(",") for pkg in ignore_pkg: issues = [issue for issue in issues if not issue['package'] == pkg] if self.ignore_src: ignore_src = self.ignore_src.split(",") for src in ignore_src: issues = [issue for issue in issues if not issue['source'] == src] if self.ignore_pending: issues = [issue for issue in issues if not issue['status'] == 'pending-fixed'] issues = [issue for issue in issues if not (issue['status'] == 'done' or issue['status'] == 'fixed')] log.debug(" Pruned down to %i.", len(issues)) for issue in issues: issue_obj = self.get_issue_for_record(issue) yield issue_obj bugwarrior-1.5.1/bugwarrior/services/redmine.py0000644000175000017500000002043213111574702023622 0ustar threebeanthreebean00000000000000import six import requests import re from bugwarrior.config import die from bugwarrior.services import Issue, IssueService, ServiceClient from taskw import TaskWarriorShellout import logging log = logging.getLogger(__name__) class RedMineClient(ServiceClient): def __init__(self, url, key, auth, issue_limit): self.url = url self.key = key self.auth = auth self.issue_limit = issue_limit def find_issues(self, issue_limit=100, only_if_assigned=False): args = {} # TODO: if issue_limit is greater than 100, implement pagination to return all issues. # Leave the implementation of this to the unlucky soul with >100 issues assigned to them. if issue_limit is not None: args["limit"] = issue_limit if only_if_assigned: args["assigned_to_id"] = 'me' return self.call_api("/issues.json", args)["issues"] def call_api(self, uri, params): url = self.url.rstrip("/") + uri kwargs = { 'headers': {'X-Redmine-API-Key': self.key}, 'params': params} if self.auth: kwargs['auth'] = self.auth return self.json_response(requests.get(url, **kwargs)) class RedMineIssue(Issue): URL = 'redmineurl' SUBJECT = 'redminesubject' ID = 'redmineid' DESCRIPTION = 'redminedescription' TRACKER = 'redminetracker' STATUS = 'redminestatus' AUTHOR = 'redmineauthor' CATEGORY = 'redminecategory' START_DATE = 'redminestartdate' SPENT_HOURS = 'redminespenthours' ESTIMATED_HOURS = 'redmineestimatedhours' CREATED_ON = 'redminecreatedon' UPDATED_ON = 'redmineupdatedon' DUEDATE = 'redmineduedate' ASSIGNED_TO = 'redmineassignedto' UDAS = { URL: { 'type': 'string', 'label': 'Redmine URL', }, SUBJECT: { 'type': 'string', 'label': 'Redmine Subject', }, ID: { 'type': 'numeric', 'label': 'Redmine ID', }, DESCRIPTION: { 'type': 'string', 'label': 'Redmine Description', }, TRACKER: { 'type': 'string', 'label': 'Redmine Tracker', }, STATUS: { 'type': 'string', 'label': 'Redmine Status', }, AUTHOR: { 'type': 'string', 'label': 'Redmine Author', }, CATEGORY: { 'type': 'string', 'label': 'Redmine Category', }, START_DATE: { 'type': 'date', 'label': 'Redmine Start Date', }, SPENT_HOURS: { 'type': 'duration', 'label': 'Redmine Spent Hours', }, ESTIMATED_HOURS: { 'type': 'duration', 'label': 'Redmine Estimated Hours', }, CREATED_ON: { 'type': 'date', 'label': 'Redmine Created On', }, UPDATED_ON: { 'type': 'date', 'label': 'Redmine Updated On', }, DUEDATE: { 'type': 'date', 'label': 'Redmine Due Date' }, ASSIGNED_TO: { 'type': 'string', 'label': 'Redmine Assigned To', }, } UNIQUE_KEY = (ID, ) PRIORITY_MAP = { 'Low': 'L', 'Normal': 'M', 'High': 'H', 'Urgent': 'H', 'Immediate': 'H', } def to_taskwarrior(self): due_date = self.record.get('due_date') start_date = self.record.get('start_date') updated_on = self.record.get('updated_on') created_on = self.record.get('created_on') spent_hours = self.record.get('spent_hours') estimated_hours = self.record.get('estimated_hours') category = self.record.get('category') assigned_to = self.record.get('assigned_to') if due_date: due_date = self.parse_date(due_date).replace(microsecond=0) if start_date: start_date = self.parse_date(start_date).replace(microsecond=0) if updated_on: updated_on = self.parse_date(updated_on).replace(microsecond=0) if created_on: created_on = self.parse_date(created_on).replace(microsecond=0) if spent_hours: spent_hours = str(spent_hours) + ' hours' spent_hours = self.get_converted_hours(spent_hours) if estimated_hours: estimated_hours = str(estimated_hours) + ' hours' estimated_hours = self.get_converted_hours(estimated_hours) if category: category = category['name'] if assigned_to: assigned_to = assigned_to['name'] return { 'project': self.get_project_name(), 'annotations': self.extra.get('annotations', []), 'priority': self.get_priority(), self.URL: self.get_issue_url(), self.SUBJECT: self.record['subject'], self.ID: self.record['id'], self.DESCRIPTION: self.record['description'], self.TRACKER: self.record['tracker']['name'], self.STATUS: self.record['status']['name'], self.AUTHOR: self.record['author']['name'], self.ASSIGNED_TO: assigned_to, self.CATEGORY: category, self.START_DATE: start_date, self.CREATED_ON: created_on, self.UPDATED_ON: updated_on, self.DUEDATE: due_date, self.ESTIMATED_HOURS: estimated_hours, self.SPENT_HOURS: spent_hours, } def get_priority(self): return self.PRIORITY_MAP.get( self.record.get('priority', {}).get('Name'), self.origin['default_priority'] ) def get_issue_url(self): return ( self.origin['url'] + "/issues/" + six.text_type(self.record["id"]) ) def get_converted_hours(self, estimated_hours): tw = TaskWarriorShellout() calc = tw._execute('calc', estimated_hours) return ( calc[0].rstrip() ) def get_project_name(self): if self.origin['project_name']: return self.origin['project_name'] # TODO: It would be nice to use the project slug (if the Redmine # instance supports it), but this would require (1) an API call # to get the list of projects, and then a look up between the # project ID contained in self.record and the list of projects. return re.sub(r'[^a-zA-Z0-9]', '', self.record["project"]["name"]).lower() def get_default_description(self): return self.build_default_description( title=self.record['subject'], url=self.get_processed_url(self.get_issue_url()), number=self.record['id'], cls='issue', ) class RedMineService(IssueService): ISSUE_CLASS = RedMineIssue CONFIG_PREFIX = 'redmine' def __init__(self, *args, **kw): super(RedMineService, self).__init__(*args, **kw) self.url = self.config.get('url').rstrip("/") self.key = self.config.get('key') self.issue_limit = self.config.get('issue_limit') login = self.config.get('login') if login: password = self.get_password('password', login) auth = (login, password) if (login and password) else None self.client = RedMineClient(self.url, self.key, auth, self.issue_limit) self.project_name = self.config.get('project_name') def get_service_metadata(self): return { 'project_name': self.project_name, 'url': self.url, } @staticmethod def get_keyring_service(service_config): url = service_config.get('url') login = service_config.get('login') return "redmine://%s@%s/%s" % (login, url) @classmethod def validate_config(cls, service_config, target): for k in ('url', 'key'): if k not in service_config: die("[%s] has no 'redmine.%s'" % (target, k)) IssueService.validate_config(service_config, target) def issues(self): only_if_assigned = self.config.get('only_if_assigned', False) issues = self.client.find_issues(self.issue_limit, only_if_assigned) log.debug(" Found %i total.", len(issues)) for issue in issues: yield self.get_issue_for_record(issue) bugwarrior-1.5.1/bugwarrior/services/youtrack.py0000644000175000017500000001331613111574702024043 0ustar threebeanthreebean00000000000000from __future__ import absolute_import import re import six import requests from jinja2 import Template from bugwarrior.config import asbool, die from bugwarrior.services import IssueService, Issue, ServiceClient import logging log = logging.getLogger(__name__) class YoutrackIssue(Issue): ISSUE = 'youtrackissue' SUMMARY = 'youtracksummary' URL = 'youtrackurl' PROJECT = 'youtrackproject' NUMBER = 'youtracknumber' UDAS = { ISSUE: { 'type': 'string', 'label': 'YouTrack Issue' }, SUMMARY: { 'type': 'string', 'label': 'YouTrack Summary', }, URL: { 'type': 'string', 'label': 'YouTrack URL', }, PROJECT: { 'type': 'string', 'label': 'YouTrack Project' }, NUMBER: { 'type': 'string', 'label': 'YouTrack Project Issue Number' }, } UNIQUE_KEY = (URL,) def _get_record_field(self, field_name): for field in self.record['field']: if field['name'] == field_name: return field def _get_record_field_value(self, field_name, field_value='value'): field = self._get_record_field(field_name) if field: return field[field_value] def to_taskwarrior(self): return { 'project': self.get_project(), 'priority': self.get_priority(), 'tags': self.get_tags(), self.ISSUE: self.get_issue(), self.SUMMARY: self.get_issue_summary(), self.URL: self.get_issue_url(), self.PROJECT: self.get_project(), self.NUMBER: self.get_number_in_project(), } def get_issue(self): return self.record['id'] def get_issue_summary(self): return self._get_record_field_value('summary') def get_issue_url(self): return "%s/issue/%s" % ( self.origin['base_url'], self.get_issue() ) def get_project(self): return self._get_record_field_value('projectShortName') def get_number_in_project(self): return int(self._get_record_field_value('numberInProject')) def get_default_description(self): return self.build_default_description( title=self.get_issue_summary(), url=self.get_processed_url(self.get_issue_url()), number=self.get_issue(), cls='issue', ) def get_tags(self): tags = [] if not self.origin['import_tags']: return tags context = self.record.copy() tag_template = Template(self.origin['tag_template']) for tag_dict in self.record.get('tag', []): context.update({ 'tag': re.sub(r'[^a-zA-Z0-9]', '_', tag_dict['value']) }) tags.append( tag_template.render(context) ) return tags class YoutrackService(IssueService, ServiceClient): ISSUE_CLASS = YoutrackIssue CONFIG_PREFIX = 'youtrack' def __init__(self, *args, **kw): super(YoutrackService, self).__init__(*args, **kw) self.host = self.config.get('host') if self.config.get('use_https', default=True, to_type=asbool): self.scheme = 'https' self.port = '443' else: self.scheme = 'http' self.port = '80' self.port = self.config.get('port', self.port) self.base_url = '%s://%s:%s' % (self.scheme, self.host, self.port) self.rest_url = self.base_url + '/rest' self.session = requests.Session() self.session.headers['Accept'] = 'application/json' self.verify_ssl = self.config.get('verify_ssl', default=True, to_type=asbool) if not self.verify_ssl: requests.packages.urllib3.disable_warnings() self.session.verify = False login = self.config.get('login') password = self.get_password('password', login) if not self.config.get('anonymous', False): self._login(login, password) self.query = self.config.get('query', default='for:me #Unresolved') self.query_limit = self.config.get('query_limit', default="100") self.import_tags = self.config.get( 'import_tags', default=True, to_type=asbool ) self.tag_template = self.config.get( 'tag_template', default='{{tag|lower}}', to_type=six.text_type ) def _login(self, login, password): resp = self.session.post(self.rest_url + "/user/login", {'login': login, 'password': password}) if resp.status_code != 200: raise RuntimeError("YouTrack responded with %s" % resp) self.session.headers['Cookie'] = resp.headers['set-cookie'] @staticmethod def get_keyring_service(service_config): host = service_config.get('host') login = service_config.get('login') return "youtrack://%s@%s" % (login, host) def get_service_metadata(self): return { 'base_url': self.base_url, 'import_tags': self.import_tags, 'tag_template': self.tag_template, } @classmethod def validate_config(cls, service_config, target): for k in ('login', 'password', 'host'): if k not in service_config: die("[%s] has no 'youtrack.%s'" % (target, k)) IssueService.validate_config(service_config, target) def issues(self): resp = self.session.get(self.rest_url + '/issue', params={'filter': self.query, 'max': self.query_limit}) issues = self.json_response(resp)['issue'] log.debug(" Found %i total.", len(issues)) for issue in issues: yield self.get_issue_for_record(issue) bugwarrior-1.5.1/bugwarrior/services/jira.py0000644000175000017500000002424213111574702023127 0ustar threebeanthreebean00000000000000from __future__ import absolute_import from builtins import str import six from jinja2 import Template from jira.client import JIRA as BaseJIRA from requests.cookies import RequestsCookieJar from bugwarrior.config import asbool, die from bugwarrior.services import IssueService, Issue import logging log = logging.getLogger(__name__) # The below `ObliviousCookieJar` and `JIRA` classes are MIT Licensed. # They were taken from this wonderful commit by @GaretJax # https://github.com/GaretJax/lancet/commit/f175cb2ec9a2135fb78188cf0b9f621b51d88977 # Prevents Jira web client being logged out when API call is made. class ObliviousCookieJar(RequestsCookieJar): def set_cookie(self, *args, **kwargs): """Simply ignore any request to set a cookie.""" pass def copy(self): """Make sure to return an instance of the correct class on copying.""" return ObliviousCookieJar() class JIRA(BaseJIRA): def _create_http_basic_session(self, *args, **kwargs): super(JIRA, self)._create_http_basic_session(*args, **kwargs) # XXX: JIRA logs the web user out if we send the session cookies we get # back from the first request in any subsequent requests. As we don't # need cookies when accessing the API anyway, just ignore all of them. self._session.cookies = ObliviousCookieJar() def close(self): self._session.close() def _parse_sprint_string(sprint): """ Parse the big ugly sprint string stored by JIRA. They look like: com.atlassian.greenhopper.service.sprint.Sprint@4c9c41a5[id=2322,rapid ViewId=1173,state=ACTIVE,name=Sprint 1,startDate=2016-09-06T16:08:07.4 55Z,endDate=2016-09-23T16:08:00.000Z,completeDate=,sequence=2322] """ entries = sprint[sprint.index('[')+1:sprint.index(']')].split(',') return dict([entry.split('=') for entry in entries]) class JiraIssue(Issue): SUMMARY = 'jirasummary' URL = 'jiraurl' FOREIGN_ID = 'jiraid' DESCRIPTION = 'jiradescription' ESTIMATE = 'jiraestimate' FIX_VERSION = 'jirafixversion' CREATED_AT = 'jiracreatedts' UDAS = { SUMMARY: { 'type': 'string', 'label': 'Jira Summary' }, URL: { 'type': 'string', 'label': 'Jira URL', }, DESCRIPTION: { 'type': 'string', 'label': 'Jira Description', }, FOREIGN_ID: { 'type': 'string', 'label': 'Jira Issue ID' }, ESTIMATE: { 'type': 'numeric', 'label': 'Estimate' }, FIX_VERSION: { 'type': 'string', 'label': 'Fix Version' }, CREATED_AT: { 'type': 'date', 'label': 'Created At' }, } UNIQUE_KEY = (URL, ) PRIORITY_MAP = { 'Highest': 'H', 'High': 'H', 'Medium': 'M', 'Low': 'L', 'Lowest': 'L', 'Trivial': 'L', 'Minor': 'L', 'Major': 'M', 'Critical': 'H', 'Blocker': 'H', } def to_taskwarrior(self): return { 'project': self.get_project(), 'priority': self.get_priority(), 'annotations': self.get_annotations(), 'tags': self.get_tags(), 'entry': self.get_entry(), self.URL: self.get_url(), self.FOREIGN_ID: self.record['key'], self.DESCRIPTION: self.record.get('fields', {}).get('description'), self.SUMMARY: self.get_summary(), self.ESTIMATE: self.get_estimate(), self.FIX_VERSION: self.get_fix_version() } def get_entry(self): created_at = self.record['fields']['created'] # Convert timestamp to an offset-aware datetime date = self.parse_date(created_at) return date def get_tags(self): return self._get_tags_from_labels() + self._get_tags_from_sprints() def _get_tags_from_sprints(self): tags = [] if not self.origin['import_sprints_as_tags']: return tags context = self.record.copy() label_template = Template(self.origin['label_template']) fields = self.record.get('fields', {}) sprints = sum([ fields.get(key) or [] for key in self.origin['sprint_field_names'] ], []) for sprint in sprints: # Parse this big ugly string. sprint = _parse_sprint_string(sprint) # Extract the name and render it into a label context.update({'label': sprint['name'].replace(' ', '')}) tags.append(label_template.render(context)) return tags def _get_tags_from_labels(self): tags = [] if not self.origin['import_labels_as_tags']: return tags context = self.record.copy() label_template = Template(self.origin['label_template']) for label in self.record.get('fields', {}).get('labels', []): context.update({'label': label}) tags.append(label_template.render(context)) return tags def get_annotations(self): return self.extra.get('annotations', []) def get_project(self): return self.record['key'].rsplit('-', 1)[0] def get_number(self): return self.record['key'].rsplit('-', 1)[1] def get_url(self): return self.origin['url'] + '/browse/' + self.record['key'] def get_summary(self): if self.extra.get('jira_version') == 4: return self.record['fields']['summary']['value'] return self.record['fields']['summary'] def get_estimate(self): if self.extra.get('jira_version') == 4: return self.record['fields']['timeestimate']['value'] try: return self.record['fields']['timeestimate'] / 60 / 60 except (TypeError, KeyError): return None def get_priority(self): value = self.record['fields'].get('priority') try: value = value['name'] except (TypeError, ): value = str(value) return self.PRIORITY_MAP.get(value, self.origin['default_priority']) def get_default_description(self): return self.build_default_description( title=self.get_summary(), url=self.get_processed_url(self.get_url()), number=self.get_number(), cls='issue', ) def get_fix_version(self): try: return self.record['fields'].get('fixVersions', [{}])[0].get('name') except (IndexError, KeyError, AttributeError, TypeError): return None class JiraService(IssueService): ISSUE_CLASS = JiraIssue CONFIG_PREFIX = 'jira' def __init__(self, *args, **kw): super(JiraService, self).__init__(*args, **kw) self.username = self.config.get('username') self.url = self.config.get('base_uri') password = self.get_password('password', self.username) default_query = 'assignee=' + self.username + \ ' AND resolution is null' self.query = self.config.get('query', default_query) if password == '@kerberos': auth = dict(kerberos=True) else: auth = dict(basic_auth=(self.username, password)) self.jira = JIRA( options={ 'server': self.config.get('base_uri'), 'rest_api_version': 'latest', 'verify': self.config.get('verify_ssl', default=True, to_type=asbool), }, **auth ) self.import_labels_as_tags = self.config.get( 'import_labels_as_tags', default=False, to_type=asbool ) self.import_sprints_as_tags = self.config.get( 'import_sprints_as_tags', default=False, to_type=asbool ) self.label_template = self.config.get( 'label_template', default='{{label}}', to_type=six.text_type ) self.sprint_field_names = [] if self.import_sprints_as_tags: field_names = [field for field in self.jira.fields() if field['name'] == 'Sprint'] if len(field_names) < 1: log.warn("No sprint custom field found. Ignoring sprints.") self.import_sprints_as_tags = False else: log.info("Found %i distinct sprint fields." % len(field_names)) self.sprint_field_names = [field['id'] for field in field_names] @staticmethod def get_keyring_service(service_config): username = service_config.get('username') base_uri = service_config.get('base_uri') return "jira://%s@%s" % (username, base_uri) def get_service_metadata(self): return { 'url': self.url, 'import_labels_as_tags': self.import_labels_as_tags, 'import_sprints_as_tags': self.import_sprints_as_tags, 'sprint_field_names': self.sprint_field_names, 'label_template': self.label_template, } @classmethod def validate_config(cls, service_config, target): for option in ('username', 'password', 'base_uri'): if option not in service_config: die("[%s] has no 'jira.%s'" % (target, option)) IssueService.validate_config(service_config, target) def annotations(self, issue, issue_obj): comments = self.jira.comments(issue.key) or [] return self.build_annotations( (( comment.author.name, comment.body ) for comment in comments), issue_obj.get_processed_url(issue_obj.get_url()) ) def issues(self): cases = self.jira.search_issues(self.query, maxResults=-1) jira_version = 5 if self.config.has_option(self.target, 'jira.version'): jira_version = self.config.getint(self.target, 'jira.version') for case in cases: issue = self.get_issue_for_record(case.raw) extra = { 'jira_version': jira_version, } if jira_version > 4: extra.update({ 'annotations': self.annotations(case, issue) }) issue.update_extra(extra) yield issue bugwarrior-1.5.1/bugwarrior/services/activecollab2.py0000644000175000017500000001530613111574702024715 0ustar threebeanthreebean00000000000000import itertools import time import six import requests from bugwarrior.services import IssueService, Issue, ServiceClient from bugwarrior.config import die import logging log = logging.getLogger(__name__) class ActiveCollab2Client(ServiceClient): def __init__(self, url, key, user_id, projects, target): self.url = url self.key = key self.user_id = user_id self.projects = projects self.target = target def get_task_dict(self, project, key, task): assigned_task = { 'project': project } if task[u'type'] == 'Ticket': # Load Ticket data # @todo Implement threading here. ticket_data = self.call_api( "/projects/" + six.text_type(task[u'project_id']) + "/tickets/" + six.text_type(task[u'ticket_id'])) assignees = ticket_data[u'assignees'] for assignee in assignees: if ( (assignee[u'is_owner'] is True) and (assignee[u'user_id'] == int(self.user_id)) ): assigned_task.update(ticket_data) return assigned_task elif task[u'type'] == 'Task': # Load Task data assigned_task.update(task) return assigned_task def get_issue_generator(self, user_id, project_id, project_name): """ Approach: 1. Get user ID from bugwarriorrc file 2. Get list of tickets from /user-tasks for a given project 3. For each ticket/task returned from #2, get ticket/task info and check if logged-in user is primary (look at `is_owner` and `user_id`) """ user_tasks_data = self.call_api( "/projects/" + six.text_type(project_id) + "/user-tasks") for key, task in enumerate(user_tasks_data): assigned_task = self.get_task_dict(project_id, key, task) if assigned_task: log.debug( " Adding '" + assigned_task['description'] + "' to task list.") yield assigned_task def call_api(self, uri): url = self.url.rstrip("/") params = { 'token': self.key, 'path_info': uri, 'format': 'json'} return self.json_response(requests.get(url, params=params)) class ActiveCollab2Issue(Issue): BODY = 'ac2body' NAME = 'ac2name' PERMALINK = 'ac2permalink' TICKET_ID = 'ac2ticketid' PROJECT_ID = 'ac2projectid' TYPE = 'ac2type' CREATED_ON = 'ac2createdon' CREATED_BY_ID = 'ac2createdbyid' UDAS = { BODY: { 'type': 'string', 'label': 'ActiveCollab2 Body' }, NAME: { 'type': 'string', 'label': 'ActiveCollab2 Name' }, PERMALINK: { 'type': 'string', 'label': 'ActiveCollab2 Permalink' }, TICKET_ID: { 'type': 'string', 'label': 'ActiveCollab2 Ticket ID' }, PROJECT_ID: { 'type': 'string', 'label': 'ActiveCollab2 Project ID' }, TYPE: { 'type': 'string', 'label': 'ActiveCollab2 Task Type' }, CREATED_ON: { 'type': 'date', 'label': 'ActiveCollab2 Created On' }, CREATED_BY_ID: { 'type': 'string', 'label': 'ActiveCollab2 Created By' }, } UNIQUE_KEY = (PERMALINK, ) PRIORITY_MAP = { -2: 'L', -1: 'L', 0: 'M', 1: 'H', 2: 'H', } def to_taskwarrior(self): record = { 'project': self.record['project'], 'priority': self.get_priority(), 'due': self.parse_date(self.record.get('due_on')), self.PERMALINK: self.record['permalink'], self.TICKET_ID: self.record['ticket_id'], self.PROJECT_ID: self.record['project_id'], self.TYPE: self.record['type'], self.CREATED_ON: self.parse_date(self.record.get('created_on')), self.CREATED_BY_ID: self.record['created_by_id'], self.BODY: self.record.get('body'), self.NAME: self.record.get('name'), } return record def get_default_description(self): record_type = self.record['type'].lower() record_type = 'issue' if record_type == 'ticket' else record_type return self.build_default_description( title=( self.record['name'] if self.record['name'] else self.record['body'] ), url=self.get_processed_url(self.record['permalink']), number=self.record['ticket_id'], cls=record_type, ) class ActiveCollab2Service(IssueService): ISSUE_CLASS = ActiveCollab2Issue CONFIG_PREFIX = 'activecollab2' def __init__(self, *args, **kw): super(ActiveCollab2Service, self).__init__(*args, **kw) self.url = self.config.get('url').rstrip('/') self.key = self.config.get('key') self.user_id = self.config.get('user_id') projects_raw = self.config.get('projects') projects_list = projects_raw.split(',') projects = [] for k, v in enumerate(projects_list): project_data = v.strip().split(":") project = dict([(project_data[0], project_data[1])]) projects.append(project) self.projects = projects self.client = ActiveCollab2Client( self.url, self.key, self.user_id, self.projects, self.target ) @classmethod def validate_config(cls, service_config, target): for k in ( 'url', 'key', 'projects', 'user_id' ): if k not in service_config: die("[%s] has no 'activecollab2.%s'" % (target, k)) super(ActiveCollab2Service, cls).validate_config(service_config, target) def issues(self): # Loop through each project start = time.time() issue_generators = [] projects = self.projects for project in projects: for project_id, project_name in project.items(): log.debug( " Getting tasks for #" + project_id + " " + project_name + '"') issue_generators.append( self.client.get_issue_generator( self.user_id, project_id, project_name ) ) log.debug(" Elapsed Time: %s" % (time.time() - start)) for record in itertools.chain(*issue_generators): yield self.get_issue_for_record(record) bugwarrior-1.5.1/bugwarrior/services/pagure.py0000644000175000017500000001514313111574702023465 0ustar threebeanthreebean00000000000000from builtins import filter import re import six import datetime import pytz import requests from jinja2 import Template from bugwarrior.config import asbool, aslist, die from bugwarrior.services import IssueService, Issue import logging log = logging.getLogger(__name__) class PagureIssue(Issue): TITLE = 'paguretitle' DATE_CREATED = 'paguredatecreated' URL = 'pagureurl' REPO = 'pagurerepo' TYPE = 'paguretype' ID = 'pagureid' UDAS = { TITLE: { 'type': 'string', 'label': 'Pagure Title', }, DATE_CREATED: { 'type': 'date', 'label': 'Pagure Created', }, REPO: { 'type': 'string', 'label': 'Pagure Repo Slug', }, URL: { 'type': 'string', 'label': 'Pagure URL', }, TYPE: { 'type': 'string', 'label': 'Pagure Type', }, ID: { 'type': 'numeric', 'label': 'Pagure Issue/PR #', }, } UNIQUE_KEY = (URL, TYPE,) def _normalize_label_to_tag(self, label): return re.sub(r'[^a-zA-Z0-9]', '_', label) def to_taskwarrior(self): if self.extra['type'] == 'pull_request': priority = 'H' else: priority = self.origin['default_priority'] return { 'project': self.extra['project'], 'priority': priority, 'annotations': self.extra.get('annotations', []), 'tags': self.get_tags(), self.URL: self.record['html_url'], self.REPO: self.record['repo'], self.TYPE: self.extra['type'], self.TITLE: self.record['title'], self.ID: self.record['id'], self.DATE_CREATED: datetime.datetime.fromtimestamp( int(self.record['date_created']), pytz.UTC), } def get_tags(self): tags = [] if not self.origin['import_tags']: return tags context = self.record.copy() tag_template = Template(self.origin['tag_template']) for tagname in self.record.get('tags', []): context.update({'label': self._normalize_label_to_tag(tagname) }) tags.append(tag_template.render(context)) return tags def get_default_description(self): return self.build_default_description( title=self.record['title'], url=self.get_processed_url(self.record['html_url']), number=self.record['id'], cls=self.extra['type'], ) class PagureService(IssueService): ISSUE_CLASS = PagureIssue CONFIG_PREFIX = 'pagure' def __init__(self, *args, **kw): super(PagureService, self).__init__(*args, **kw) self.session = requests.Session() self.tag = self.config.get('tag') self.repo = self.config.get('repo') self.base_url = self.config.get('base_url') self.exclude_repos = self.config.get('exclude_repos', [], aslist) self.include_repos = self.config.get('include_repos', [], aslist) self.import_tags = self.config.get( 'import_tags', default=False, to_type=asbool ) self.tag_template = self.config.get( 'tag_template', default='{{label}}', to_type=six.text_type ) def get_service_metadata(self): return { 'import_tags': self.import_tags, 'tag_template': self.tag_template, } def get_issues(self, repo, keys): """ Grab all the issues """ key1, key2 = keys key3 = key1[:-1] # Just the singular form of key1 url = self.base_url + "/api/0/" + repo + "/" + key1 response = self.session.get(url) if not bool(response): error = response.json() code = error['error_code'] if code == 'ETRACKERDISABLED': return [] else: raise IOError('Failed to talk to %r %r' % (url, error)) issues = [] for result in response.json()[key2]: idx = six.text_type(result['id']) result['html_url'] = "/".join([self.base_url, repo, key3, idx]) issues.append((repo, result)) return issues def annotations(self, issue, issue_obj): url = issue['html_url'] return self.build_annotations( (( c['user']['name'], c['comment'], ) for c in issue['comments']), issue_obj.get_processed_url(url) ) def get_owner(self, issue): if issue[1]['assignee']: return issue[1]['assignee']['name'] def filter_repos(self, repo): if self.exclude_repos: if repo in self.exclude_repos: return False if self.include_repos: if repo in self.include_repos: return True else: return False return True def issues(self): if self.tag: url = self.base_url + "/api/0/projects?tags=" + self.tag response = self.session.get(url) if not bool(response): raise IOError('Failed to talk to %r %r' % (url, response)) all_repos = [r['name'] for r in response.json()['projects']] else: all_repos = [self.repo] repos = filter(self.filter_repos, all_repos) issues = [] for repo in repos: issues.extend(self.get_issues(repo, ('issues', 'issues'))) issues.extend(self.get_issues(repo, ('pull-requests', 'requests'))) log.debug(" Found %i issues.", len(issues)) issues = list(filter(self.include, issues)) log.debug(" Pruned down to %i issues.", len(issues)) for repo, issue in issues: # Stuff this value into the upstream dict for: # https://pagure.com/ralphbean/bugwarrior/issues/159 issue['repo'] = repo issue_obj = self.get_issue_for_record(issue) extra = { 'project': repo, 'type': 'pull_request' if 'branch' in issue else 'issue', 'annotations': self.annotations(issue, issue_obj) } issue_obj.update_extra(extra) yield issue_obj @classmethod def validate_config(cls, service_config, target): if 'tag' not in service_config and 'repo' not in service_config: die("[%s] has no 'pagure.tag' or 'pagure.repo'" % target) if 'base_url' not in service_config: die("[%s] has no 'pagure.base_url'" % target) super(PagureService, cls).validate_config(service_config, target) bugwarrior-1.5.1/bugwarrior/services/gerrit.py0000644000175000017500000001121413111574702023471 0ustar threebeanthreebean00000000000000from __future__ import absolute_import import json import os import requests from bugwarrior.config import die from bugwarrior.services import IssueService, Issue, ServiceClient class GerritIssue(Issue): SUMMARY = 'gerritsummary' URL = 'gerriturl' FOREIGN_ID = 'gerritid' BRANCH = 'gerritbranch' TOPIC = 'gerrittopic' UDAS = { SUMMARY: { 'type': 'string', 'label': 'Gerrit Summary' }, URL: { 'type': 'string', 'label': 'Gerrit URL', }, FOREIGN_ID: { 'type': 'numeric', 'label': 'Gerrit Change ID' }, BRANCH: { 'type': 'string', 'label': 'Gerrit Branch', }, TOPIC: { 'type': 'string', 'label': 'Gerrit Topic', }, } UNIQUE_KEY = (URL, ) def to_taskwarrior(self): return { 'project': self.record['project'], 'annotations': self.extra['annotations'], self.URL: self.extra['url'], 'priority': self.origin['default_priority'], 'tags': [], self.FOREIGN_ID: self.record['_number'], self.SUMMARY: self.record['subject'], self.BRANCH: self.record['branch'], self.TOPIC: self.record.get('topic', 'notopic'), } def get_default_description(self): return self.build_default_description( title=self.record['subject'], url=self.get_processed_url(self.extra['url']), number=self.record['_number'], cls='pull_request', ) class GerritService(IssueService, ServiceClient): ISSUE_CLASS = GerritIssue CONFIG_PREFIX = 'gerrit' def __init__(self, *args, **kw): super(GerritService, self).__init__(*args, **kw) self.url = self.config.get('base_uri').strip('/') self.username = self.config.get('username') self.password = self.get_password('password', self.username) self.ssl_ca_path = self.config.get('ssl_ca_path', None) self.session = requests.session() self.session.headers.update({ 'Accept': 'application/json', 'Accept-Encoding': 'gzip', }) if self.ssl_ca_path: self.session.verify = os.path.expanduser(self.ssl_ca_path) # uses digest authentication if supported by the server, fallback to basic # gerrithub.io supports only basic response = self.session.head(self.url + '/a/') if 'digest' in response.headers.get('www-authenticate', '').lower(): self.session.auth = requests.auth.HTTPDigestAuth( self.username, self.password) else: self.session.auth = requests.auth.HTTPBasicAuth( self.username, self.password) @staticmethod def get_keyring_service(service_config): base_uri = service_config.get('base_uri') return "gerrit://%s" % base_uri def get_service_metadata(self): return { 'url': self.url, } @classmethod def validate_config(cls, service_config, target): for option in ('username', 'password', 'base_uri'): if option not in service_config: die("[%s] has no 'gerrit.%s'" % (target, option)) IssueService.validate_config(service_config, target) def issues(self): # Construct the whole url by hand here, because otherwise requests will # percent-encode the ':' characters, which gerrit doesn't like. url = self.url + '/a/changes/' + \ '?q=is:open+is:reviewer' + \ '&o=MESSAGES&o=DETAILED_ACCOUNTS' response = self.session.get(url) response.raise_for_status() # The response has some ")]}'" garbage prefixed. body = response.text[4:] changes = json.loads(body) for change in changes: extra = { 'url': self.build_url(change), 'annotations': self.annotations(change), } yield self.get_issue_for_record(change, extra) def build_url(self, change): return '%s/#/c/%i/' % (self.url, change['_number']) def annotations(self, change): entries = [] for item in change['messages']: username = item['author']['username'] # Gerrit messages are really messy message = item['message']\ .lstrip('Patch Set ')\ .lstrip("%s:" % item['_revision_number'])\ .strip()\ .replace('\n', ' ') entries.append((username, message,)) return self.build_annotations(entries, self.build_url(change)) bugwarrior-1.5.1/bugwarrior/services/activecollab.py0000644000175000017500000002047513111574702024636 0ustar threebeanthreebean00000000000000from builtins import object import re import pypandoc from pyac.library import activeCollab from bugwarrior.services import IssueService, Issue from bugwarrior.config import die import logging log = logging.getLogger(__name__) class ActiveCollabClient(object): def __init__(self, url, key, user_id): self.url = url self.key = key self.user_id = int(user_id) self.activecollabtivecollab = activeCollab( key=key, url=url, user_id=user_id ) class ActiveCollabIssue(Issue): BODY = 'acbody' NAME = 'acname' PERMALINK = 'acpermalink' TASK_ID = 'actaskid' FOREIGN_ID = 'acid' PROJECT_ID = 'acprojectid' PROJECT_NAME = 'acprojectname' TYPE = 'actype' CREATED_ON = 'accreatedon' CREATED_BY_NAME = 'accreatedbyname' ESTIMATED_TIME = 'acestimatedtime' TRACKED_TIME = 'actrackedtime' MILESTONE = 'acmilestone' LABEL = 'aclabel' UDAS = { BODY: { 'type': 'string', 'label': 'ActiveCollab Body' }, NAME: { 'type': 'string', 'label': 'ActiveCollab Name' }, PERMALINK: { 'type': 'string', 'label': 'ActiveCollab Permalink' }, TASK_ID: { 'type': 'numeric', 'label': 'ActiveCollab Task ID' }, FOREIGN_ID: { 'type': 'numeric', 'label': 'ActiveCollab ID', }, PROJECT_ID: { 'type': 'numeric', 'label': 'ActiveCollab Project ID' }, PROJECT_NAME: { 'type': 'string', 'label': 'ActiveCollab Project Name' }, TYPE: { 'type': 'string', 'label': 'ActiveCollab Task Type' }, CREATED_ON: { 'type': 'date', 'label': 'ActiveCollab Created On' }, CREATED_BY_NAME: { 'type': 'string', 'label': 'ActiveCollab Created By' }, ESTIMATED_TIME: { 'type': 'numeric', 'label': 'ActiveCollab Estimated Time' }, TRACKED_TIME: { 'type': 'numeric', 'label': 'ActiveCollab Tracked Time' }, MILESTONE: { 'type': 'string', 'label': 'ActiveCollab Milestone' }, LABEL: { 'type': 'string', 'label': 'ActiveCollab Label' } } UNIQUE_KEY = (FOREIGN_ID, ) def to_taskwarrior(self): record = { 'project': re.sub(r'\W+', '-', self.record['project']).lower(), 'priority': self.get_priority(), 'annotations': self.extra.get('annotations', []), self.NAME: self.record.get('name', ''), self.BODY: pypandoc.convert(self.record.get('body'), 'md', format='html').rstrip(), self.PERMALINK: self.record['permalink'], self.TASK_ID: int(self.record.get('task_id')), self.PROJECT_NAME: self.record['project'], self.PROJECT_ID: int(self.record['project_id']), self.FOREIGN_ID: int(self.record['id']), self.TYPE: self.record.get('type', 'subtask').lower(), self.CREATED_BY_NAME: self.record['created_by_name'], self.MILESTONE: self.record['milestone'], self.ESTIMATED_TIME: self.record.get('estimated_time', 0), self.TRACKED_TIME: self.record.get('tracked_time', 0), self.LABEL: self.record.get('label'), } if self.TYPE == 'subtask': # Store the parent task ID for subtasks record['actaskid'] = int(self.record['task_id']) if isinstance(self.record.get('due_on'), dict): record['due'] = self.parse_date( self.record.get('due_on')['formatted_date'] ) if isinstance(self.record.get('created_on'), dict): record[self.CREATED_ON] = self.parse_date( self.record.get('created_on')['formatted_date'] ) return record def get_annotations(self): return self.extra.get('annotations', []) def get_priority(self): value = self.record.get('priority') if value > 0: return 'H' elif value < 0: return 'L' else: return 'M' def get_default_description(self): return self.build_default_description( title=( self.record.get('name') if self.record.get('name') else self.record.get('body') ), url=self.get_processed_url(self.record['permalink']), number=self.record['id'], cls=self.record.get('type', 'subtask').lower(), ) class ActiveCollabService(IssueService): ISSUE_CLASS = ActiveCollabIssue CONFIG_PREFIX = 'activecollab' def __init__(self, *args, **kw): super(ActiveCollabService, self).__init__(*args, **kw) self.url = self.config.get('url').rstrip('/') self.key = self.config.get('key') self.user_id = int(self.config.get('user_id')) self.client = ActiveCollabClient( self.url, self.key, self.user_id ) self.activecollab = activeCollab(url=self.url, key=self.key, user_id=self.user_id) @classmethod def validate_config(cls, service_config, target): for k in ('url', 'key', 'user_id'): if k not in service_config: die("[%s] has no 'activecollab.%s'" % (target, k)) IssueService.validate_config(service_config, target) def _comments(self, issue): comments = self.activecollab.get_comments( issue['project_id'], issue['task_id'] ) comments_formatted = [] if comments is not None: for comment in comments: comments_formatted.append( dict(user=comment['created_by']['display_name'], body=comment['body'])) return comments_formatted def get_owner(self, issue): if issue['assignee_id']: return issue['assignee_id'] def annotations(self, issue, issue_obj): if 'type' not in issue: # Subtask return [] comments = self._comments(issue) if comments is None: return [] return self.build_annotations( (( c['user'], pypandoc.convert(c['body'], 'md', format='html').rstrip() ) for c in comments), issue_obj.get_processed_url(issue_obj.record['permalink']), ) def issues(self): data = self.activecollab.get_my_tasks() label_data = self.activecollab.get_assignment_labels() labels = dict() for item in label_data: labels[item['id']] = re.sub(r'\W+', '_', item['name']) task_count = 0 issues = [] for key, record in data.items(): for task_id, task in record['assignments'].items(): task_count = task_count + 1 # Add tasks if task['assignee_id'] == self.user_id: task['label'] = labels.get(task['label_id']) issues.append(task) if 'subtasks' in task: for subtask_id, subtask in task['subtasks'].items(): # Add subtasks task_count = task_count + 1 if subtask['assignee_id'] is self.user_id: # Add some data from the parent task subtask['label'] = labels.get(subtask['label_id']) subtask['project_id'] = task['project_id'] subtask['project'] = task['project'] subtask['task_id'] = task['task_id'] subtask['milestone'] = task['milestone'] issues.append(subtask) log.debug(" Found %i total", task_count) log.debug(" Pruned down to %i", len(issues)) for issue in issues: issue_obj = self.get_issue_for_record(issue) extra = { 'annotations': self.annotations(issue, issue_obj) } issue_obj.update_extra(extra) yield issue_obj bugwarrior-1.5.1/bugwarrior/services/github.py0000644000175000017500000003431713111574702023470 0ustar threebeanthreebean00000000000000from builtins import filter import re import six from urllib.parse import urlparse import requests from six.moves.urllib.parse import quote_plus from jinja2 import Template from bugwarrior.config import asbool, aslist, die from bugwarrior.services import IssueService, Issue, ServiceClient import logging log = logging.getLogger(__name__) class GithubClient(ServiceClient): def __init__(self, host, auth): self.host = host self.auth = auth self.session = requests.Session() if 'token' in self.auth: authorization = 'token ' + self.auth['token'] self.session.headers['Authorization'] = authorization def _api_url(self, path, **context): """ Build the full url to the API endpoint """ if self.host == 'github.com': baseurl = "https://api.github.com" else: baseurl = "https://{}/api/v3".format(self.host) return baseurl + path.format(**context) def get_repos(self, username): user_repos = self._getter(self._api_url("/user/repos?per_page=100")) public_repos = self._getter(self._api_url( "/users/{username}/repos?per_page=100", username=username)) return user_repos + public_repos def get_query(self, query): """Run a generic issue/PR query""" url = self._api_url( "/search/issues?q={query}&per_page=100", query=query) return self._getter(url, subkey='items') def get_issues(self, username, repo): url = self._api_url( "/repos/{username}/{repo}/issues?per_page=100", username=username, repo=repo) return self._getter(url) def get_directly_assigned_issues(self): """ Returns all issues assigned to authenticated user. This will return all issues assigned to the authenticated user regardless of whether the user owns the repositories in which the issues exist. """ url = self._api_url("/user/issues?per_page=100") return self._getter(url) def get_comments(self, username, repo, number): url = self._api_url( "/repos/{username}/{repo}/issues/{number}/comments?per_page=100", username=username, repo=repo, number=number) return self._getter(url) def get_pulls(self, username, repo): url = self._api_url( "/repos/{username}/{repo}/pulls?per_page=100", username=username, repo=repo) return self._getter(url) def _getter(self, url, subkey=None): """ Pagination utility. Obnoxious. """ kwargs = {} if 'basic' in self.auth: kwargs['auth'] = self.auth['basic'] results = [] link = dict(next=url) while 'next' in link: response = self.session.get(link['next'], **kwargs) # Warn about the mis-leading 404 error code. See: # https://github.com/ralphbean/bugwarrior/issues/374 if response.status_code == 404 and 'token' in self.auth: log.warn("A '404' from github may indicate an auth " "failure. Make sure both that your token is correct " "and that it has 'public_repo' and not 'public " "access' rights.") json_res = self.json_response(response) if subkey is not None: json_res = json_res[subkey] results += json_res link = self._link_field_to_dict(response.headers.get('link', None)) return results @staticmethod def _link_field_to_dict(field): """ Utility for ripping apart github's Link header field. It's kind of ugly. """ if not field: return dict() return dict([ ( part.split('; ')[1][5:-1], part.split('; ')[0][1:-1], ) for part in field.split(', ') ]) class GithubIssue(Issue): TITLE = 'githubtitle' BODY = 'githubbody' CREATED_AT = 'githubcreatedon' UPDATED_AT = 'githubupdatedat' MILESTONE = 'githubmilestone' URL = 'githuburl' REPO = 'githubrepo' TYPE = 'githubtype' NUMBER = 'githubnumber' USER = 'githubuser' UDAS = { TITLE: { 'type': 'string', 'label': 'Github Title', }, BODY: { 'type': 'string', 'label': 'Github Body', }, CREATED_AT: { 'type': 'date', 'label': 'Github Created', }, UPDATED_AT: { 'type': 'date', 'label': 'Github Updated', }, MILESTONE: { 'type': 'string', 'label': 'Github Milestone', }, REPO: { 'type': 'string', 'label': 'Github Repo Slug', }, URL: { 'type': 'string', 'label': 'Github URL', }, TYPE: { 'type': 'string', 'label': 'Github Type', }, NUMBER: { 'type': 'numeric', 'label': 'Github Issue/PR #', }, USER: { 'type': 'string', 'label': 'Github User', }, } UNIQUE_KEY = (URL, TYPE,) def _normalize_label_to_tag(self, label): return re.sub(r'[^a-zA-Z0-9]', '_', label) def to_taskwarrior(self): milestone = self.record['milestone'] if milestone: milestone = milestone['title'] body = self.record['body'] if body: body = body.replace('\r\n', '\n') if self.extra['type'] == 'pull_request': priority = 'H' else: priority = self.origin['default_priority'] return { 'project': self.extra['project'], 'priority': priority, 'annotations': self.extra.get('annotations', []), 'tags': self.get_tags(), self.URL: self.record['html_url'], self.REPO: self.record['repo'], self.TYPE: self.extra['type'], self.USER: self.record['user']['login'], self.TITLE: self.record['title'], self.BODY: body, self.MILESTONE: milestone, self.NUMBER: self.record['number'], self.CREATED_AT: self.parse_date(self.record['created_at']), self.UPDATED_AT: self.parse_date(self.record['updated_at']) } def get_tags(self): tags = [] if not self.origin['import_labels_as_tags']: return tags context = self.record.copy() label_template = Template(self.origin['label_template']) for label_dict in self.record.get('labels', []): context.update({ 'label': self._normalize_label_to_tag(label_dict['name']) }) tags.append( label_template.render(context) ) return tags def get_default_description(self): return self.build_default_description( title=self.record['title'], url=self.get_processed_url(self.record['html_url']), number=self.record['number'], cls=self.extra['type'], ) class GithubService(IssueService): ISSUE_CLASS = GithubIssue CONFIG_PREFIX = 'github' def __init__(self, *args, **kw): super(GithubService, self).__init__(*args, **kw) self.host = self.config.get('host', 'github.com') self.login = self.config.get('login') auth = {} token = self.config.get('token') if 'token' in self.config: token = self.get_password('token', self.login) auth['token'] = token else: password = self.get_password('password', self.login) auth['basic'] = (self.login, password) self.client = GithubClient(self.host, auth) self.exclude_repos = self.config.get('exclude_repos', [], aslist) self.include_repos = self.config.get('include_repos', [], aslist) self.username = self.config.get('username') self.filter_pull_requests = self.config.get( 'filter_pull_requests', default=False, to_type=asbool ) self.involved_issues = self.config.get( 'involved_issues', default=False, to_type=asbool ) self.import_labels_as_tags = self.config.get( 'import_labels_as_tags', default=False, to_type=asbool ) self.label_template = self.config.get( 'label_template', default='{{label}}', to_type=six.text_type ) self.query = self.config.get( 'query', default='involves: {user} state:open'.format( user=self.username) if self.involved_issues else '', to_type=six.text_type ) @staticmethod def get_keyring_service(service_config): login = service_config.get('login') username = service_config.get('username') host = service_config.get('host', default='github.com') return "github://{login}@{host}/{username}".format( login=login, username=username, host=host) def get_service_metadata(self): return { 'import_labels_as_tags': self.import_labels_as_tags, 'label_template': self.label_template, } def get_owned_repo_issues(self, tag): """ Grab all the issues """ issues = {} for issue in self.client.get_issues(*tag.split('/')): issues[issue['url']] = (tag, issue) return issues def get_query(self, query): """ Grab all issues matching a github query """ issues = {} for issue in self.client.get_query(query): url = issue['html_url'] try: repo = self.get_repository_from_issue(issue) except ValueError as e: log.critical(e) else: issues[url] = (repo, issue) return issues def get_directly_assigned_issues(self): issues = {} for issue in self.client.get_directly_assigned_issues(): repos = self.get_repository_from_issue(issue) issues[issue['url']] = (repos, issue) return issues @classmethod def get_repository_from_issue(cls, issue): if 'repo' in issue: return issue['repo'] if 'repos_url' in issue: url = issue['repos_url'] elif 'repository_url' in issue: url = issue['repository_url'] else: raise ValueError("Issue has no repository url" + str(issue)) tag = re.match('.*/([^/]*/[^/]*)$', url) if tag is None: raise ValueError("Unrecognized URL: {}.".format(url)) return tag.group(1) def _comments(self, tag, number): user, repo = tag.split('/') return self.client.get_comments(user, repo, number) def annotations(self, tag, issue, issue_obj): url = issue['html_url'] annotations = [] if self.annotation_comments: comments = self._comments(tag, issue['number']) log.debug(" got comments for %s", issue['html_url']) annotations = (( c['user']['login'], c['body'], ) for c in comments) return self.build_annotations( annotations, issue_obj.get_processed_url(url) ) def _reqs(self, tag): """ Grab all the pull requests """ return [ (tag, i) for i in self.client.get_pulls(*tag.split('/')) ] def get_owner(self, issue): if issue[1]['assignee']: return issue[1]['assignee']['login'] def filter_issues(self, issue): repo, _ = issue return self.filter_repo_name(repo.split('/')[-3]) def filter_repos(self, repo): if repo['owner']['login'] != self.username: return False return self.filter_repo_name(repo['name']) def filter_repo_name(self, name): if self.exclude_repos: if name in self.exclude_repos: return False if self.include_repos: if name in self.include_repos: return True else: return False return True def include(self, issue): if 'pull_request' in issue[1] and not self.filter_pull_requests: return True return super(GithubService, self).include(issue) def issues(self): issues = {} if self.query: issues.update(self.get_query(self.query)) if self.config.get('include_user_repos', True, asbool): all_repos = self.client.get_repos(self.username) assert(type(all_repos) == list) repos = filter(self.filter_repos, all_repos) for repo in repos: issues.update( self.get_owned_repo_issues( self.username + "/" + repo['name']) ) if self.config.get('include_user_issues', True, asbool): issues.update( filter(self.filter_issues, self.get_directly_assigned_issues().items()) ) log.debug(" Found %i issues.", len(issues)) issues = list(filter(self.include, issues.values())) log.debug(" Pruned down to %i issues.", len(issues)) for tag, issue in issues: # Stuff this value into the upstream dict for: # https://github.com/ralphbean/bugwarrior/issues/159 issue['repo'] = tag issue_obj = self.get_issue_for_record(issue) extra = { 'project': tag.split('/')[1], 'type': 'pull_request' if 'pull_request' in issue else 'issue', 'annotations': self.annotations(tag, issue, issue_obj) } issue_obj.update_extra(extra) yield issue_obj @classmethod def validate_config(cls, service_config, target): if 'login' not in service_config: die("[%s] has no 'github.login'" % target) if 'token' not in service_config and 'password' not in service_config: die("[%s] has no 'github.token' or 'github.password'" % target) if 'username' not in service_config: die("[%s] has no 'github.username'" % target) super(GithubService, cls).validate_config(service_config, target) bugwarrior-1.5.1/bugwarrior/services/teamlab.py0000644000175000017500000001036213111574702023605 0ustar threebeanthreebean00000000000000import six import requests from bugwarrior.config import die from bugwarrior.services import Issue, IssueService, ServiceClient import logging log = logging.getLogger(__name__) class TeamLabClient(ServiceClient): def __init__(self, hostname, verbose=False): self.hostname = hostname self.verbose = verbose self.token = None def authenticate(self, login, password): resp = self.call_api("/api/1.0/authentication.json", post={ "userName": six.text_type(login), "password": six.text_type(password), }) self.token = six.text_type(resp["token"]) def get_task_list(self): resp = self.call_api("/api/1.0/project/task/@self.json") return resp def call_api(self, uri, post=None, params=None): uri = "http://" + self.hostname + uri kwargs = {'params': params} if self.token: kwargs['headers'] = {'Authorization': self.token} response = (requests.post(uri, data=post, **kwargs) if post else requests.get(uri, **kwargs)) return self.json_response(response) class TeamLabIssue(Issue): URL = 'teamlaburl' FOREIGN_ID = 'teamlabid' TITLE = 'teamlabtitle' PROJECTOWNER_ID = 'teamlabprojectownerid' UDAS = { URL: { 'type': 'string', 'label': 'Teamlab URL', }, FOREIGN_ID: { 'type': 'string', 'label': 'Teamlab ID', }, TITLE: { 'type': 'string', 'label': 'Teamlab Title', }, PROJECTOWNER_ID: { 'type': 'string', 'label': 'Teamlab ProjectOwner ID', } } UNIQUE_KEY = (URL, ) def to_taskwarrior(self): return { 'project': self.get_project(), 'priority': self.get_priority(), self.TITLE: self.record['title'], self.FOREIGN_ID: self.record['id'], self.URL: self.get_issue_url(), self.PROJECTOWNER_ID: self.record['projectOwner']['id'], } def get_default_description(self): return self.build_default_description( title=self.record['title'], url=self.get_processed_url(self.get_issue_url()), number=self.record['id'], cls='issue', ) def get_project(self): return self.origin['project_name'] def get_issue_url(self): return "http://%s/products/projects/tasks.aspx?prjID=%d&id=%d" % ( self.origin['hostname'], self.record["projectOwner"]["id"], self.record["id"] ) def get_priority(self): if self.record.get("priority") == 1: return "H" return self.origin['default_priority'] class TeamLabService(IssueService): ISSUE_CLASS = TeamLabIssue CONFIG_PREFIX = 'teamlab' def __init__(self, *args, **kw): super(TeamLabService, self).__init__(*args, **kw) self.hostname = self.config.get('hostname') _login = self.config.get('login') _password = self.get_password('password', _login) self.client = TeamLabClient(self.hostname) self.client.authenticate(_login, _password) self.project_name = self.config.get('project_name', self.hostname) @staticmethod def get_keyring_service(service_config): login = service_config.get('login') hostname = service_config.get('hostname') return "teamlab://%s@%s" % (login, hostname) def get_service_metadata(self): return { 'hostname': self.hostname, 'project_name': self.project_name, } @classmethod def validate_config(cls, service_config, target): for k in ('login', 'password', 'hostname'): if k not in service_config: die("[%s] has no 'teamlab.%s'" % (target, k)) IssueService.validate_config(service_config, target) def issues(self): issues = self.client.get_task_list() log.debug(" Remote has %i total issues.", len(issues)) # Filter out closed tasks. issues = [i for i in issues if i["status"] == 1] log.debug(" Remote has %i active issues.", len(issues)) for issue in issues: yield self.get_issue_for_record(issue) bugwarrior-1.5.1/bugwarrior/services/__init__.py0000644000175000017500000004603713111574702023747 0ustar threebeanthreebean00000000000000from __future__ import unicode_literals from builtins import str from builtins import object import copy import multiprocessing import time from pkg_resources import iter_entry_points from dateutil.parser import parse as parse_date from dateutil.tz import tzlocal from jinja2 import Template import pytz import six from taskw.task import Task from bugwarrior.config import asbool, die, get_service_password, ServiceConfig from bugwarrior.db import MARKUP, URLShortener import logging log = logging.getLogger(__name__) # Sentinels for process completion status SERVICE_FINISHED_OK = 0 SERVICE_FINISHED_ERROR = 1 # Used by `parse_date` as a timezone when you would like a naive # date string to be parsed as if it were in your local timezone LOCAL_TIMEZONE = 'LOCAL_TIMEZONE' def get_service(service_name): epoint = iter_entry_points(group='bugwarrior.service', name=service_name) try: epoint = next(epoint) except StopIteration: return None return epoint.load() class IssueService(object): """ Abstract base class for each service """ # Which class should this service instantiate for holding these issues? ISSUE_CLASS = None # What prefix should we use for this service's configuration values CONFIG_PREFIX = '' def __init__(self, main_config, main_section, target): self.config = ServiceConfig(self.CONFIG_PREFIX, main_config, target) self.main_section = main_section self.target = target self.desc_len = 35 if main_config.has_option(self.main_section, 'description_length'): self.desc_len = main_config.getint( self.main_section, 'description_length') self.anno_len = 45 if main_config.has_option(self.main_section, 'annotation_length'): self.anno_len = main_config.getint( self.main_section, 'annotation_length') self.inline_links = True if main_config.has_option(self.main_section, 'inline_links'): self.inline_links = asbool(main_config.get( self.main_section, 'inline_links')) self.annotation_links = not self.inline_links if main_config.has_option(self.main_section, 'annotation_links'): self.annotation_links = asbool( main_config.get(self.main_section, 'annotation_links') ) self.annotation_comments = True if main_config.has_option(self.main_section, 'annotation_comments'): self.annotation_comments = asbool( main_config.get(self.main_section, 'annotation_comments') ) self.shorten = False if main_config.has_option(self.main_section, 'shorten'): self.shorten = asbool(main_config.get(self.main_section, 'shorten')) self.add_tags = [] if 'add_tags' in self.config: for raw_option in self.config.get('add_tags').split(','): option = raw_option.strip(' +;') if option: self.add_tags.append(option) self.default_priority = 'M' if 'default_priority' in self.config: self.default_priority = self.config.get('default_priority') log.info("Working on [%s]", self.target) def get_templates(self): """ Get any defined templates for configuration values. Users can override the value of any Taskwarrior field using this feature on a per-key basis. The key should be the name of the field to you would like to configure the value of, followed by '_template', and the value should be a Jinja template generating the field's value. As context variables, all fields on the taskwarrior record are available. For example, to prefix the returned project name for tickets returned by a service with 'workproject_', you could add an entry reading: project_template = workproject_{{project}} Or, if you'd simply like to override the returned project name for all tickets incoming from a specific service, you could add an entry like: project_template = myprojectname The above would cause all issues to recieve a project name of 'myprojectname', regardless of what the project name of the generated issue was. """ templates = {} for key in six.iterkeys(Task.FIELDS): template_key = '%s_template' % key if template_key in self.config: templates[key] = self.config.get(template_key) return templates def get_password(self, key, login='nousername'): password = self.config.get(key) keyring_service = self.get_keyring_service(self.config) if not password or password.startswith("@oracle:"): password = get_service_password( keyring_service, login, oracle=password, interactive=self.config.interactive) return password def get_service_metadata(self): return {} def get_issue_for_record(self, record, extra=None): origin = { 'annotation_length': self.anno_len, 'default_priority': self.default_priority, 'description_length': self.desc_len, 'templates': self.get_templates(), 'target': self.target, 'shorten': self.shorten, 'inline_links': self.inline_links, 'add_tags': self.add_tags, } origin.update(self.get_service_metadata()) return self.ISSUE_CLASS(record, origin=origin, extra=extra) def build_annotations(self, annotations, url): final = [] if self.annotation_links: final.append(url) if self.annotation_comments: for author, message in annotations: message = message.strip() if not message or not author: continue message = message.replace('\n', '').replace('\r', '') if self.anno_len: message = '%s%s' % ( message[:self.anno_len], '...' if len(message) > self.anno_len else '' ) final.append('@%s - %s' % (author, message)) return final @classmethod def validate_config(cls, service_config, target): """ Validate generic options for a particular target """ if service_config.has_option(target, 'only_if_assigned'): die("[%s] has an 'only_if_assigned' option. Should be " "'%s.only_if_assigned'." % (target, cls.CONFIG_PREFIX)) if service_config.has_option(target, 'also_unassigned'): die("[%s] has an 'also_unassigned' option. Should be " "'%s.also_unassigned'." % (target, cls.CONFIG_PREFIX)) def include(self, issue): """ Return true if the issue in question should be included """ only_if_assigned = self.config.get('only_if_assigned', None) if only_if_assigned: owner = self.get_owner(issue) include_owners = [only_if_assigned] if self.config.get('also_unassigned', None, asbool): include_owners.append(None) return owner in include_owners only_if_author = self.config.get('only_if_author', None) if only_if_author: return self.get_author(issue) == only_if_author return True def get_owner(self, issue): """ Override this for filtering on tickets """ raise NotImplementedError() def get_author(self, issue): """ Override this for filtering on tickets """ raise NotImplementedError() def issues(self): """ Returns a list of dicts representing issues from a remote service. This is the main place to begin if you are implementing a new service for bugwarrior. Override this to gather issues for each service. Each item in the list should be a dict that looks something like this: { "description": "Some description of the issue", "project": "some_project", "priority": "H", "annotations": [ "This is an annotation", "This is another annotation", ] } The description can be 'anything' but must be consistent and unique for issues you're pulling from a remote service. You can and should use the ``.description(...)`` method to help format your descriptions. The project should be a string and may be anything you like. The priority should be one of "H", "M", or "L". """ raise NotImplementedError() @staticmethod def get_keyring_service(service_config): """ Given the keyring service name for this service. """ raise NotImplementedError @six.python_2_unicode_compatible class Issue(object): # Set to a dictionary mapping UDA short names with type and long name. # # Example:: # # { # 'project_id': { # 'type': 'string', # 'label': 'Project ID', # }, # 'ticket_number': { # 'type': 'number', # 'label': 'Ticket Number', # }, # } # # Note: For best results, dictionary keys should be unique! UDAS = {} # Should be a tuple of field names (can be UDA names) that are usable for # uniquely identifying an issue in the foreign system. UNIQUE_KEY = [] # Should be a dictionary of value-to-level mappings between the foreign # system and the string values 'H', 'M' or 'L'. PRIORITY_MAP = {} def __init__(self, foreign_record, origin=None, extra=None): self._foreign_record = foreign_record self._origin = origin if origin else {} self._extra = extra if extra else {} def update_extra(self, extra): self._extra.update(extra) def to_taskwarrior(self): """ Transform a foreign record into a taskwarrior dictionary.""" raise NotImplementedError() def get_default_description(self): """ Return the old-style verbose description from bugwarrior. This is useful for two purposes: * Finding and linking historically-created records. * Allowing people to keep using the historical description for taskwarrior. """ raise NotImplementedError() def get_added_tags(self): added_tags = [] for tag in self.origin['add_tags']: tag = Template(tag).render(self.get_template_context()) if tag: added_tags.append(tag) return added_tags def get_taskwarrior_record(self, refined=True): if not getattr(self, '_taskwarrior_record', None): self._taskwarrior_record = self.to_taskwarrior() record = copy.deepcopy(self._taskwarrior_record) if refined: record = self.refine_record(record) if not 'tags' in record: record['tags'] = [] if refined: record['tags'].extend(self.get_added_tags()) return record def get_priority(self): return self.PRIORITY_MAP.get( self.record.get('priority'), self.origin['default_priority'] ) def get_processed_url(self, url): """ Returns a URL with conditional processing. If the following config key are set: - [general]shorten returns a shortened URL; otherwise returns the URL unaltered. """ if self.origin['shorten']: return URLShortener().shorten(url) return url def parse_date(self, date, timezone='UTC'): """ Parse a date string into a datetime object. :param `date`: A time string parseable by `dateutil.parser.parse` :param `timezone`: The string timezone name (from `pytz.all_timezones`) to use as a default should the parsed time string not include timezone information. """ if date: date = parse_date(date) if not date.tzinfo: if timezone == LOCAL_TIMEZONE: tzinfo = tzlocal() else: tzinfo = pytz.timezone(timezone) date = date.replace(tzinfo=tzinfo) return date return None def build_default_description( self, title='', url='', number='', cls="issue" ): cls_markup = { 'issue': 'Is', 'pull_request': 'PR', 'merge_request': 'MR', 'todo': '', 'task': '', 'subtask': 'Subtask #', } url_separator = ' .. ' url = url if self.origin['inline_links'] else '' desc_len = self.origin['description_length'] return u"%s%s#%s - %s%s%s" % ( MARKUP, cls_markup[cls], number, title[:desc_len] if desc_len else title, url_separator if url else '', url, ) def _get_unique_identifier(self): record = self.get_taskwarrior_record() return dict([ (key, record[key],) for key in self.UNIQUE_KEY ]) def get_template_context(self): context = ( self.get_taskwarrior_record(refined=False).copy() ) context.update(self.extra) context.update({ 'description': self.get_default_description(), }) return context def refine_record(self, record): for field in six.iterkeys(Task.FIELDS): if field in self.origin['templates']: template = Template(self.origin['templates'][field]) record[field] = template.render(self.get_template_context()) elif hasattr(self, 'get_default_%s' % field): record[field] = getattr(self, 'get_default_%s' % field)() return record def __iter__(self): record = self.get_taskwarrior_record() for key in six.iterkeys(record): yield key def keys(self): return list(self.__iter__()) def iterkeys(self): return self.__iter__() def items(self): record = self.get_taskwarrior_record() return list(six.iteritems(record)) def iteritems(self): record = self.get_taskwarrior_record() for item in six.iteritems(record): yield item def update(self, *args): raise AttributeError( "You cannot set attributes on issues." ) def get(self, attribute, default=None): try: return self[attribute] except KeyError: return default def __getitem__(self, attribute): record = self.get_taskwarrior_record() return record[attribute] def __setitem__(self, attribute, value): raise AttributeError( "You cannot set attributes on issues." ) def __delitem__(self, attribute): raise AttributeError( "You cannot delete attributes from issues." ) @property def record(self): return self._foreign_record @property def extra(self): return self._extra @property def origin(self): return self._origin def __str__(self): return '%s: %s' % ( self.origin['target'], self.get_taskwarrior_record()['description'] ) def __repr__(self): return '<%s>' % str(self) class ServiceClient(object): """ Abstract class responsible for making requests to service API's. """ @staticmethod def json_response(response): # If we didn't get good results, just bail. if response.status_code != 200: raise IOError( "Non-200 status code %r; %r; %r" % ( response.status_code, response.url, response.text, )) if callable(response.json): # Newer python-requests return response.json() else: # Older python-requests return response.json def _aggregate_issues(conf, main_section, target, queue, service_name): """ This worker function is separated out from the main :func:`aggregate_issues` func only so that we can use multiprocessing on it for speed reasons. """ start = time.time() try: service = get_service(service_name)(conf, main_section, target) issue_count = 0 for issue in service.issues(): queue.put(issue) issue_count += 1 except SystemExit as e: log.critical(str(e)) queue.put((SERVICE_FINISHED_ERROR, (target, e))) except BaseException as e: if hasattr(e, 'request') and e.request: # Exceptions raised by requests library have the HTTP request # object stored as attribute. The request can have hooks attached # to it, and we need to remove them, as there can be unpickleable # methods. There is no one left to call these hooks anyway. e.request.hooks = {} log.exception("Worker for [%s] failed: %s" % (target, e)) queue.put((SERVICE_FINISHED_ERROR, (target, e))) else: queue.put((SERVICE_FINISHED_OK, (target, issue_count, ))) finally: duration = time.time() - start log.info("Done with [%s] in %fs" % (target, duration)) def aggregate_issues(conf, main_section, debug): """ Return all issues from every target. """ log.info("Starting to aggregate remote issues.") # Create and call service objects for every target in the config targets = [t.strip() for t in conf.get(main_section, 'targets').split(',')] queue = multiprocessing.Queue() log.info("Spawning %i workers." % len(targets)) processes = [] if debug: for target in targets: _aggregate_issues( conf, main_section, target, queue, conf.get(target, 'service') ) else: for target in targets: proc = multiprocessing.Process( target=_aggregate_issues, args=(conf, main_section, target, queue, conf.get(target, 'service')) ) proc.start() processes.append(proc) # Sleep for 1 second here to try and avoid a race condition where # all N workers start up and ask the gpg-agent process for # information at the same time. This causes gpg-agent to fumble # and tell some of our workers some incomplete things. time.sleep(1) currently_running = len(targets) while currently_running > 0: issue = queue.get(True) if isinstance(issue, tuple): completion_type, args = issue if completion_type == SERVICE_FINISHED_ERROR: target, e = args log.info("Terminating workers") for process in processes: process.terminate() raise RuntimeError( "critical error in target '{}'".format(target)) currently_running -= 1 continue yield issue log.info("Done aggregating remote issues.") bugwarrior-1.5.1/bugwarrior/services/versionone.py0000644000175000017500000002140013111574702024362 0ustar threebeanthreebean00000000000000from v1pysdk import V1Meta from v1pysdk.none_deref import NoneDeref from six.moves.urllib import parse from bugwarrior.services import IssueService, Issue, LOCAL_TIMEZONE from bugwarrior.config import die class VersionOneIssue(Issue): TASK_NAME = 'versiononetaskname' TASK_DESCRIPTION = 'versiononetaskdescrption' TASK_ESTIMATE = 'versiononetaskestimate' TASK_DETAIL_ESTIMATE = 'versiononetaskdetailestimate' TASK_TO_DO = 'versiononetasktodo' TASK_REFERENCE = 'versiononetaskreference' TASK_URL = 'versiononetaskurl' TASK_OID = 'versiononetaskoid' STORY_NAME = 'versiononestoryname' STORY_DESCRIPTION = 'versiononestorydescription' STORY_ESTIMATE = 'versiononestoryestimate' STORY_DETAIL_ESTIMATE = 'versiononestorydetailestimate' STORY_URL = 'versiononestoryurl' STORY_NUMBER = 'versiononestorynumber' STORY_OID = 'versiononestoryoid' TIMEBOX_BEGIN_DATE = 'versiononetimeboxbegindate' TIMEBOX_END_DATE = 'versiononetimeboxenddate' TIMEBOX_NAME = 'versiononetimeboxname' UDAS = { TASK_NAME: { 'type': 'string', 'label': 'VersionOne Task Name' }, TASK_DESCRIPTION: { 'type': 'string', 'label': 'VersionOne Task Description' }, TASK_ESTIMATE: { 'type': 'string', 'label': 'VersionOne Task Estimate' }, TASK_DETAIL_ESTIMATE: { 'type': 'string', 'label': 'VersionOne Task Detail Estimate', }, TASK_TO_DO: { 'type': 'string', 'label': 'VersionOne Task To Do' }, TASK_REFERENCE: { 'type': 'string', 'label': 'VersionOne Task Reference' }, TASK_URL: { 'type': 'string', 'label': 'VersionOne Task URL' }, TASK_OID: { 'type': 'string', 'label': 'VersionOne Task Object ID' }, STORY_NAME: { 'type': 'string', 'label': 'VersionOne Story Name' }, STORY_DESCRIPTION: { 'type': 'string', 'label': 'VersionOne Story Description' }, STORY_ESTIMATE: { 'type': 'string', 'label': 'VersionOne Story Estimate' }, STORY_DETAIL_ESTIMATE: { 'type': 'string', 'label': 'VersionOne Story Detail Estimate' }, STORY_URL: { 'type': 'string', 'label': 'VersionOne Story URL' }, STORY_NUMBER: { 'type': 'string', 'label': 'VersionOne Story Number' }, STORY_OID: { 'type': 'string', 'label': 'VersionOne Story Object ID' }, TIMEBOX_BEGIN_DATE: { 'type': 'string', 'label': 'VersionOne Timebox Begin Date' }, TIMEBOX_END_DATE: { 'type': 'string', 'label': 'VersionOne Timebox End Date' }, TIMEBOX_NAME: { 'type': 'string', 'label': 'VersionOne Timebox Name' } } UNIQUE_KEY = (TASK_URL, ) def to_taskwarrior(self): return { 'project': self.extra['project'], 'priority': self.origin['default_priority'], 'due': self.parse_date( self.record['timebox']['EndDate'], self.origin['timezone'] ), self.TASK_NAME: self.record['task']['Name'], self.TASK_DESCRIPTION: self.record['task']['Description'], self.TASK_ESTIMATE: self.record['task']['Estimate'], self.TASK_DETAIL_ESTIMATE: self.record['task']['DetailEstimate'], self.TASK_TO_DO: self.record['task']['ToDo'], self.TASK_REFERENCE: self.record['task']['Reference'], self.TASK_URL: self.record['task']['url'], self.TASK_OID: self.record['task']['idref'], self.STORY_NAME: self.record['story']['Name'], self.STORY_DESCRIPTION: self.record['story']['Description'], self.STORY_ESTIMATE: self.record['story']['Estimate'], self.STORY_DETAIL_ESTIMATE: self.record['story']['DetailEstimate'], self.STORY_URL: self.record['story']['url'], self.STORY_OID: self.record['story']['idref'], self.STORY_NUMBER: self.record['story']['Number'], self.TIMEBOX_BEGIN_DATE: self.record['timebox']['BeginDate'], self.TIMEBOX_END_DATE: self.record['timebox']['EndDate'], self.TIMEBOX_NAME: self.record['timebox']['Name'], } def get_default_description(self): return self.build_default_description( title=': '.join([ self.record['story']['Name'], self.record['task']['Name'], ]), url=self.record['task']['url'], number=self.record['story']['Number'], cls='task', ) class VersionOneService(IssueService): ISSUE_CLASS = VersionOneIssue CONFIG_PREFIX = 'versionone' TASK_COLLECT_DATA = ( 'Name', 'Description', 'Estimate', 'DetailEstimate', 'ToDo', 'Reference', 'url', 'idref', ) STORY_COLLECT_DATA = ( 'Name', 'Description', 'Estimate', 'DetailEstimate', 'Number', 'url', 'idref', ) TIMEBOX_COLLECT_DATA = ( 'BeginDate', 'EndDate', 'Name', ) def __init__(self, *args, **kw): super(VersionOneService, self).__init__(*args, **kw) parsed_address = parse.urlparse( self.config.get('base_uri') ) self.address = parsed_address.netloc self.instance = parsed_address.path.strip('/') self.username = self.config.get('username') self.password = self.get_password('password', self.username) self.timezone = self.config.get('timezone', default=LOCAL_TIMEZONE) self.project = self.config.get('project_name', default='') self.timebox_name = self.config.get('timebox_name') @staticmethod def get_keyring_service(service_config): parsed_address = parse.urlparse(service_config.get('base_uri')) username = service_config.get('username') return "versionone://%s@%s%s" % ( username, parsed_address.netloc, parsed_address.path ) def get_service_metadata(self): return { 'timezone': self.timezone } @classmethod def validate_config(cls, service_config, target): options = ( 'base_uri', 'username', ) for option in options: if option not in service_config: die("[%s] has no 'versionone.%s'" % (target, option)) IssueService.validate_config(service_config, target) def get_meta(self): if not hasattr(self, '_meta'): self._meta = V1Meta( address=self.address, instance=self.instance, username=self.username, password=self.password ) return self._meta def get_assignments(self, username): meta = self.get_meta() where = { 'IsClosed': False, 'IsCompleted': False, 'IsDead': False, 'IsDeleted': False, 'IsInactive': False, } if self.timebox_name: where['Parent.Timebox.Name'] = self.timebox_name tasks = meta.Task.select( 'Name', 'Parent', 'Description', 'Estimate', 'DetailEstimate', 'ToDo', 'Reference', ).filter( "Owners.Username='{username}'".format(username=self.username) ).where(**where) return tasks def issues(self): for issue in self.get_assignments(self.username): issue_data = { 'task': {}, 'story': {}, 'timebox': {}, } field_maps = ( ('task', issue, self.TASK_COLLECT_DATA, ), ('story', issue.Parent, self.STORY_COLLECT_DATA, ), ('timebox', issue.Parent.Timebox, self.TIMEBOX_COLLECT_DATA, ), ) for key, source, columns in field_maps: for column in columns: value = getattr(source, column, None) # NoneDeref is a special kind of None used by the v1 client if isinstance(value, NoneDeref): value = None issue_data[key][column] = value extras = { 'project': self.project } yield self.get_issue_for_record( issue_data, extras ) bugwarrior-1.5.1/bugwarrior/data.py0000644000175000017500000000202213111574702021260 0ustar threebeanthreebean00000000000000import os import json from lockfile.pidlockfile import PIDLockFile class BugwarriorData(object): def __init__(self, data_path): self.datafile = os.path.join(data_path, 'bugwarrior.data') self.lockfile = os.path.join(data_path, 'bugwarrior-data.lockfile') def get_data(self): with open(self.datafile, 'r') as jsondata: return json.load(jsondata) def get(self, key): try: return self.get_data()[key] except IOError: # File does not exist. return None def set(self, key, value): with PIDLockFile(self.lockfile): try: data = self.get_data() except IOError: # File does not exist. with open(self.datafile, 'w') as jsondata: json.dump({key: value}, jsondata) else: with open(self.datafile, 'w') as jsondata: data[key] = value json.dump(data, jsondata) os.chmod(self.datafile, 0o600) bugwarrior-1.5.1/bugwarrior/notifications.py0000644000175000017500000000713113111575107023226 0ustar threebeanthreebean00000000000000from future import standard_library standard_library.install_aliases() import datetime import os import urllib.request, urllib.parse, urllib.error import warnings from bugwarrior.config import asbool cache_dir = os.path.expanduser(os.getenv('XDG_CACHE_HOME', "~/.cache") + "/bugwarrior") logo_path = cache_dir + "/logo.png" logo_url = "https://upload.wikimedia.org/wikipedia/" + \ "en/5/59/Taskwarrior_logo.png" def _cache_logo(): if os.path.exists(logo_path): return if not os.path.isdir(cache_dir): os.makedirs(cache_dir) urllib.request.urlretrieve(logo_url, logo_path) def _get_metadata(issue): due = '' tags = '' priority = '' metadata = '' project = '' if 'project' in issue: project = "Project: " + issue['project'] # if 'due' in issue: # due = "Due: " + datetime.datetime.fromtimestamp( # int(issue['due'])).strftime('%Y-%m-%d') if 'tags' in issue: tags = "Tags: " + ', '.join(issue['tags']) if 'priority' in issue: priority = "Priority: " + issue['priority'] if project != '': metadata += "\n" + project if priority != '': metadata += "\n" + priority if due != '': metadata += "\n" + due if tags != '': metadata += "\n" + tags return metadata def send_notification(issue, op, conf): notify_backend = conf.get('notifications', 'backend') if notify_backend == 'pynotify': warnings.warn("pynotify is deprecated. Use backend=gobject. " "See https://github.com/ralphbean/bugwarrior/issues/336") notify_backend = 'gobject' # Notifications for growlnotify on Mac OS X if notify_backend == 'growlnotify': import gntp.notifier growl = gntp.notifier.GrowlNotifier( applicationName="Bugwarrior", notifications=["New Updates", "New Messages"], defaultNotifications=["New Messages"], ) growl.register() if op == 'bw_finished': growl.notify( noteType="New Messages", title="Bugwarrior", description="Finished querying for new issues.\n%s" % issue['description'], sticky=asbool(conf.get( 'notifications', 'finished_querying_sticky', 'True')), icon="https://upload.wikimedia.org/wikipedia/" "en/5/59/Taskwarrior_logo.png", priority=1, ) return message = "%s task: %s" % (op, issue['description']) metadata = _get_metadata(issue) if metadata is not None: message += metadata growl.notify( noteType="New Messages", title="Bugwarrior", description=message, sticky=asbool(conf.get( 'notifications', 'task_crud_sticky', 'True')), icon="https://upload.wikimedia.org/wikipedia/" "en/5/59/Taskwarrior_logo.png", priority=1, ) return elif notify_backend == 'gobject': _cache_logo() import gi gi.require_version('Notify', '0.7') from gi.repository import Notify Notify.init("bugwarrior") if op == 'bw finished': message = "Finished querying for new issues.\n%s" %\ issue['description'] else: message = "%s task: %s" % (op, issue['description']) metadata = _get_metadata(issue) if metadata is not None: message += metadata Notify.Notification.new("Bugwarrior", message, logo_path).show() bugwarrior-1.5.1/bugwarrior/db.py0000644000175000017500000004230713111574702020746 0ustar threebeanthreebean00000000000000from __future__ import unicode_literals from future import standard_library standard_library.install_aliases() from builtins import zip from builtins import object from configparser import NoOptionError, NoSectionError import os import re import subprocess import sys import requests import dogpile.cache import six from taskw import TaskWarriorShellout from taskw.exceptions import TaskwarriorError from bugwarrior.config import asbool, get_taskrc_path from bugwarrior.notifications import send_notification import logging log = logging.getLogger(__name__) MARKUP = "(bw)" # In Python 2.3 through 2.7, the stdlib dbm module include a berkeley db # interface, which was used by default by dogpile.cache. In Python3, the # berkeley db module was removed which means that cache files created by # bugwarrior on python 2 will not be compatible with cache files as read by # bugwarrior on python 3. Attempting to read them generates a traceback. # To work around this, we use different filenames for py2 and py3 here. # https://github.com/ralphbean/bugwarrior/pull/416 PYVER = '%i.%i' % sys.version_info[:2] DOGPILE_CACHE_PATH = os.path.expanduser(''.join([ os.getenv('XDG_CACHE_HOME', '~/.cache'), '/dagd-py', PYVER, '.dbm'])) if not os.path.isdir(os.path.dirname(DOGPILE_CACHE_PATH)): os.makedirs(os.path.dirname(DOGPILE_CACHE_PATH)) CACHE_REGION = dogpile.cache.make_region().configure( "dogpile.cache.dbm", arguments=dict(filename=DOGPILE_CACHE_PATH), ) class URLShortener(object): _instance = None def __new__(cls, *args, **kwargs): if not cls._instance: cls._instance = super(URLShortener, cls).__new__( cls, *args, **kwargs ) return cls._instance @CACHE_REGION.cache_on_arguments() def shorten(self, url): if not url: return '' base = 'https://da.gd/s' return requests.get(base, params=dict(url=url)).text.strip() class NotFound(Exception): pass class MultipleMatches(Exception): pass def get_normalized_annotation(annotation): return re.sub( r'[\W_]', '', six.text_type(annotation) ) def get_annotation_hamming_distance(left, right): left = get_normalized_annotation(left) right = get_normalized_annotation(right) if len(left) > len(right): left = left[0:len(right)] elif len(right) > len(left): right = right[0:len(left)] return hamdist(left, right) def hamdist(str1, str2): """Count the # of differences between equal length strings str1 and str2""" diffs = 0 for ch1, ch2 in zip(str1, str2): if ch1 != ch2: diffs += 1 return diffs def get_managed_task_uuids(tw, key_list, legacy_matching): expected_task_ids = set([]) for keys in key_list.values(): tasks = tw.filter_tasks({ 'and': [('%s.any' % key, None) for key in keys], 'or': [ ('status', 'pending'), ('status', 'waiting'), ], }) expected_task_ids = expected_task_ids | set([ task['uuid'] for task in tasks ]) if legacy_matching: starts_with_markup = tw.filter_tasks({ 'description.startswith': MARKUP, 'or': [ ('status', 'pending'), ('status', 'waiting'), ], }) expected_task_ids = expected_task_ids | set([ task['uuid'] for task in starts_with_markup ]) return expected_task_ids def find_local_uuid(tw, keys, issue, legacy_matching=False): """ For a given issue issue, find its local UUID. Assembles a list of task IDs existing in taskwarrior matching the supplied issue (`issue`) on the combination of any set of supplied unique identifiers (`keys`) or, optionally, the task's description field (should `legacy_matching` be `True`). :params: * `tw`: An instance of `taskw.TaskWarriorShellout` * `keys`: A list of lists of keys to use for uniquely identifying an issue. To clarify the "list of lists" behavior, assume that there are two services, one having a single primary key field -- 'serviceAid' -- and another having a pair of fields composing its primary key -- 'serviceBproject' and 'serviceBnumber' --, the incoming data for this field would be:: [ ['serviceAid'], ['serviceBproject', 'serviceBnumber'], ] * `issue`: An instance of a subclass of `bugwarrior.services.Issue`. * `legacy_matching`: By default, this is disabled, and it allows the matching algorithm to -- in addition to searching by stored issue keys -- search using the task's description for a match. It is prone to error and should avoided if possible. :returns: * A single string UUID. :raises: * `bugwarrior.db.MultipleMatches`: if multiple matches were found. * `bugwarrior.db.NotFound`: if an issue was not found. """ if not issue['description']: raise ValueError('Issue %s has no description.' % issue) possibilities = set([]) if legacy_matching: legacy_description = issue.get_default_description().rsplit('..', 1)[0] # Furthermore, we have to kill off any single quotes which break in # task-2.4.x, as much as it saddens me. legacy_description = legacy_description.split("'")[0] results = tw.filter_tasks({ 'description.startswith': legacy_description, 'or': [ ('status', 'pending'), ('status', 'waiting'), ], }) possibilities = possibilities | set([ task['uuid'] for task in results ]) for service, key_list in six.iteritems(keys): if any([key in issue for key in key_list]): results = tw.filter_tasks({ 'and': [("%s.is" % key, issue[key]) for key in key_list], 'or': [ ('status', 'pending'), ('status', 'waiting'), ], }) possibilities = possibilities | set([ task['uuid'] for task in results ]) if len(possibilities) == 1: return possibilities.pop() if len(possibilities) > 1: raise MultipleMatches( "Issue %s matched multiple IDs: %s" % ( issue['description'], possibilities ) ) raise NotFound( "No issue was found matching %s" % issue ) def merge_left(field, local_task, remote_issue, hamming=False): """ Merge array field from the remote_issue into local_task * Local 'left' entries are preserved without modification * Remote 'left' are appended to task if not present in local. :param `field`: Task field to merge. :param `local_task`: `taskw.task.Task` object into which to merge remote changes. :param `remote_issue`: `dict` instance from which to merge into local task. :param `hamming`: (default `False`) If `True`, compare entries by truncating to maximum length, and comparing hamming distances. Useful generally only for annotations. """ # Ensure that empty defaults are present local_field = local_task.get(field, []) remote_field = remote_issue.get(field, []) # We need to make sure an array exists for this field because # we will be appending to it in a moment. if field not in local_task: local_task[field] = [] # If a remote does not appear in local, add it to the local task new_count = 0 for remote in remote_field: for local in local_field: if ( # For annotations, they don't have to match *exactly*. ( hamming and get_annotation_hamming_distance(remote, local) == 0 ) # But for everything else, they should. or ( remote == local ) ): break else: log.debug("%s not found in %r" % (remote, local_field)) local_task[field].append(remote) new_count += 1 if new_count > 0: log.debug('Added %s new values to %s (total: %s)' % ( new_count, field, len(local_task[field]),)) def run_hooks(conf, name): if conf.has_option('hooks', name): pre_import = [ t.strip() for t in conf.get('hooks', name).split(',') ] if pre_import is not None: for hook in pre_import: exit_code = subprocess.call(hook, shell=True) if exit_code is not 0: msg = 'Non-zero exit code %d on hook %s' % ( exit_code, hook ) log.error(msg) raise RuntimeError(msg) def synchronize(issue_generator, conf, main_section, dry_run=False): def _bool_option(section, option, default): try: return asbool(conf.get(section, option)) except (NoSectionError, NoOptionError): return default targets = [t.strip() for t in conf.get(main_section, 'targets').split(',')] services = set([conf.get(target, 'service') for target in targets]) key_list = build_key_list(services) uda_list = build_uda_config_overrides(services) if uda_list: log.info( 'Service-defined UDAs exist: you can optionally use the ' '`bugwarrior-uda` command to export a list of UDAs you can ' 'add to your taskrc file.' ) static_fields = ['priority'] if conf.has_option(main_section, 'static_fields'): static_fields = conf.get(main_section, 'static_fields').split(',') # Before running CRUD operations, call the pre_import hook(s). run_hooks(conf, 'pre_import') notify = _bool_option('notifications', 'notifications', False) and not dry_run tw = TaskWarriorShellout( config_filename=get_taskrc_path(conf, main_section), config_overrides=uda_list, marshal=True, ) legacy_matching = _bool_option(main_section, 'legacy_matching', False) merge_annotations = _bool_option(main_section, 'merge_annotations', True) merge_tags = _bool_option(main_section, 'merge_tags', True) issue_updates = { 'new': [], 'existing': [], 'changed': [], 'closed': get_managed_task_uuids(tw, key_list, legacy_matching), } for issue in issue_generator: try: issue_dict = dict(issue) # We received this issue from The Internet, but we're not sure what # kind of encoding the service providers may have handed us. Let's try # and decode all byte strings from UTF8 off the bat. If we encounter # other encodings in the wild in the future, we can revise the handling # here. https://github.com/ralphbean/bugwarrior/issues/350 for key in issue_dict.keys(): if isinstance(issue_dict[key], six.binary_type): try: issue_dict[key] = issue_dict[key].decode('utf-8') except UnicodeDecodeError: log.warn("Failed to interpret %r as utf-8" % key) existing_uuid = find_local_uuid( tw, key_list, issue, legacy_matching=legacy_matching ) _, task = tw.get_task(uuid=existing_uuid) # Drop static fields from the upstream issue. We don't want to # overwrite local changes to fields we declare static. for field in static_fields: del issue_dict[field] # Merge annotations & tags from online into our task object if merge_annotations: merge_left('annotations', task, issue_dict, hamming=True) if merge_tags: merge_left('tags', task, issue_dict) issue_dict.pop('annotations', None) issue_dict.pop('tags', None) task.update(issue_dict) if task.get_changes(keep=True): issue_updates['changed'].append(task) else: issue_updates['existing'].append(task) if existing_uuid in issue_updates['closed']: issue_updates['closed'].remove(existing_uuid) except MultipleMatches as e: log.exception("Multiple matches: %s", six.text_type(e)) except NotFound: issue_updates['new'].append(issue_dict) notreally = ' (not really)' if dry_run else '' # Add new issues log.info("Adding %i tasks", len(issue_updates['new'])) for issue in issue_updates['new']: log.info(u"Adding task %s%s", issue['description'], notreally) if dry_run: continue if notify: send_notification(issue, 'Created', conf) try: tw.task_add(**issue) except TaskwarriorError as e: log.exception("Unable to add task: %s" % e.stderr) log.info("Updating %i tasks", len(issue_updates['changed'])) for issue in issue_updates['changed']: changes = '; '.join([ '{field}: {f} -> {t}'.format( field=field, f=repr(ch[0]), t=repr(ch[1]) ) for field, ch in six.iteritems(issue.get_changes(keep=True)) ]) log.info( "Updating task %s, %s; %s%s", six.text_type(issue['uuid']), issue['description'], changes, notreally ) if dry_run: continue try: tw.task_update(issue) except TaskwarriorError as e: log.exception("Unable to modify task: %s" % e.stderr) log.info("Closing %i tasks", len(issue_updates['closed'])) for issue in issue_updates['closed']: _, task_info = tw.get_task(uuid=issue) log.info( "Completing task %s %s%s", issue, task_info.get('description', ''), notreally ) if dry_run: continue if notify: send_notification(task_info, 'Completed', conf) try: tw.task_done(uuid=issue) except TaskwarriorError as e: log.exception("Unable to close task: %s" % e.stderr) # Send notifications if notify: only_on_new_tasks = _bool_option('notifications', 'only_on_new_tasks', False) if not only_on_new_tasks or len(issue_updates['new']) + len(issue_updates['changed']) + len(issue_updates['closed']) > 0: send_notification( dict( description="New: %d, Changed: %d, Completed: %d" % ( len(issue_updates['new']), len(issue_updates['changed']), len(issue_updates['closed']) ) ), 'bw_finished', conf, ) def build_key_list(targets): from bugwarrior.services import get_service keys = {} for target in targets: keys[target] = get_service(target).ISSUE_CLASS.UNIQUE_KEY return keys def get_defined_udas_as_strings(conf, main_section): targets = [t.strip() for t in conf.get(main_section, 'targets').split(',')] services = set([conf.get(target, 'service') for target in targets]) uda_list = build_uda_config_overrides(services) for uda in convert_override_args_to_taskrc_settings(uda_list): yield uda def build_uda_config_overrides(targets): """ Returns a list of UDAs defined by given targets For all targets in `targets`, build a dictionary of configuration overrides representing the UDAs defined by the passed-in services (`targets`). Given a hypothetical situation in which you have two services, the first of which defining a UDA named 'serviceAid' ("Service A ID", string) and a second service defining two UDAs named 'serviceBproject' ("Service B Project", string) and 'serviceBnumber' ("Service B Number", numeric), this would return the following structure:: { 'uda': { 'serviceAid': { 'label': 'Service A ID', 'type': 'string', }, 'serviceBproject': { 'label': 'Service B Project', 'type': 'string', }, 'serviceBnumber': { 'label': 'Service B Number', 'type': 'numeric', } } } """ from bugwarrior.services import get_service targets_udas = {} for target in targets: targets_udas.update(get_service(target).ISSUE_CLASS.UDAS) return { 'uda': targets_udas } def convert_override_args_to_taskrc_settings(config, prefix=''): args = [] for k, v in six.iteritems(config): if isinstance(v, dict): args.extend( convert_override_args_to_taskrc_settings( v, prefix='.'.join([ prefix, k, ]) if prefix else k ) ) else: v = six.text_type(v) left = (prefix + '.' if prefix else '') + k args.append('='.join([left, v])) return args bugwarrior-1.5.1/bugwarrior/config.py0000644000175000017500000002334413111574702021626 0ustar threebeanthreebean00000000000000from future import standard_library standard_library.install_aliases() import codecs from configparser import ConfigParser import os import subprocess import sys import six import logging log = logging.getLogger(__name__) from bugwarrior.data import BugwarriorData # The name of the environment variable that can be used to ovewrite the path # to the bugwarriorrc file BUGWARRIORRC = "BUGWARRIORRC" def asbool(some_value): """ Cast config values to boolean. """ return six.text_type(some_value).lower() in [ 'y', 'yes', 't', 'true', '1', 'on' ] def aslist(value): """ Cast config values to lists of strings """ return [item.strip() for item in value.strip().split(',')] def get_keyring(): """ Try to import and return optional keyring dependency. """ try: import keyring except ImportError: raise ImportError( "Extra dependencies must be installed to use the keyring feature. " "Install them with `pip install bugwarrior[keyring]`.") return keyring def get_service_password(service, username, oracle=None, interactive=False): """ Retrieve the sensitive password for a service by: * retrieving password from a secure store (@oracle:use_keyring, default) * asking the password from the user (@oracle:ask_password, interactive) * executing a command and use the output as password (@oracle:eval:) Note that the keyring may or may not be locked which requires that the user provides a password (interactive mode). :param service: Service name, may be key into secure store (as string). :param username: Username for the service (as string). :param oracle: Hint which password oracle strategy to use. :return: Retrieved password (as string) .. seealso:: https://bitbucket.org/kang/python-keyring-lib """ import getpass password = None if not oracle or oracle == "@oracle:use_keyring": keyring = get_keyring() password = keyring.get_password(service, username) if interactive and password is None: # -- LEARNING MODE: Password is not stored in keyring yet. oracle = "@oracle:ask_password" password = get_service_password(service, username, oracle, interactive=True) if password: keyring.set_password(service, username, password) elif interactive and oracle == "@oracle:ask_password": prompt = "%s password: " % service password = getpass.getpass(prompt) elif oracle.startswith('@oracle:eval:'): command = oracle[13:] return oracle_eval(command) if password is None: die("MISSING PASSWORD: oracle='%s', interactive=%s for service=%s" % (oracle, interactive, service)) return password def oracle_eval(command): """ Retrieve password from the given command """ p = subprocess.Popen( command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) p.wait() if p.returncode == 0: return p.stdout.readline().strip().decode('utf-8') else: die( "Error retrieving password: `{command}` returned '{error}'".format( command=command, error=p.stderr.read().strip())) def load_example_rc(): fname = os.path.join( os.path.dirname(__file__), 'docs/configuration.rst' ) with open(fname, 'r') as f: readme = f.read() example = readme.split('.. example')[1][4:] return example error_template = """ ************************************************* * There was a problem with your bugwarriorrc * * {error_msg} * Here's an example template to help: * ************************************************* {example}""" def die(error_msg): log.critical(error_template.format( error_msg=error_msg, example=load_example_rc(), )) sys.exit(1) def validate_config(config, main_section): if not config.has_section(main_section): die("No [%s] section found." % main_section) logging.basicConfig( level=getattr(logging, config.get(main_section, 'log.level')), filename=config.get(main_section, 'log.file'), ) # In general, its nice to log "everything", but some of the loggers from # our dependencies are very very spammy. Here, we silence most of their # noise: spammers = [ 'bugzilla.base', 'bugzilla.bug', 'requests.packages.urllib3.connectionpool', ] for spammer in spammers: logging.getLogger(spammer).setLevel(logging.WARN) if not config.has_option(main_section, 'targets'): die("No targets= item in [%s] found." % main_section) targets = config.get(main_section, 'targets') targets = [t for t in [t.strip() for t in targets.split(",")] if len(t)] if not targets: die("Empty targets= item in [%s]." % main_section) for target in targets: if target not in config.sections(): die("No [%s] section found." % target) # Validate each target one by one. for target in targets: service = config.get(target, 'service') if not service: die("No 'service' in [%s]" % target) if not get_service(service): die("'%s' in [%s] is not a valid service." % (service, target)) # Call the service-specific validator service = get_service(service) service_config = ServiceConfig(service.CONFIG_PREFIX, config, target) service.validate_config(service_config, target) def get_config_path(): """ Determine the path to the config file. This will return, in this order of precedence: - the value of $BUGWARRIORRC if set - $XDG_CONFIG_HOME/bugwarrior/bugwarriorc if exists - ~/.bugwarriorrc if exists - /bugwarrior/bugwarriorc if exists, for dir in $XDG_CONFIG_DIRS - $XDG_CONFIG_HOME/bugwarrior/bugwarriorc otherwise """ if os.environ.get(BUGWARRIORRC): return os.environ[BUGWARRIORRC] xdg_config_home = ( os.environ.get('XDG_CONFIG_HOME') or os.path.expanduser('~/.config')) xdg_config_dirs = ( (os.environ.get('XDG_CONFIG_DIRS') or '/etc/xdg').split(':')) paths = [ os.path.join(xdg_config_home, 'bugwarrior', 'bugwarriorrc'), os.path.expanduser("~/.bugwarriorrc")] paths += [ os.path.join(d, 'bugwarrior', 'bugwarriorrc') for d in xdg_config_dirs] for path in paths: if os.path.exists(path): return path return paths[0] def load_config(main_section, interactive=False): config = BugwarriorConfigParser({'log.level': "INFO", 'log.file': None}) path = get_config_path() config.readfp(codecs.open(path, "r", "utf-8",)) config.interactive = interactive config.data = BugwarriorData(get_data_path(config, main_section)) validate_config(config, main_section) return config def get_taskrc_path(conf, main_section): path = os.getenv('TASKRC', '~/.taskrc') if conf.has_option(main_section, 'taskrc'): path = conf.get(main_section, 'taskrc') return os.path.normpath( os.path.expanduser(path) ) def get_data_path(config, main_section): taskrc = get_taskrc_path(config, main_section) # We cannot use the taskw module here because it doesn't really support # the `_` subcommands properly (`rc:` can't be used for them). line_prefix = 'data.location=' env={ 'PATH': os.getenv('PATH'), 'TASKRC': taskrc, } # If TASKDATA is set but empty, taskwarrior's data.location is empty. taskdata = os.getenv('TASKDATA') if taskdata: env['TASKDATA'] = taskdata tw_show = subprocess.Popen( ('task', '_show'), stdout=subprocess.PIPE, env=env) data_location = subprocess.check_output( ('grep', '-e', '^' + line_prefix), stdin=tw_show.stdout) tw_show.wait() data_path = data_location[len(line_prefix):].rstrip().decode('utf-8') if not data_path: raise RuntimeError('Unable to determine the data location.') return os.path.normpath(os.path.expanduser(data_path)) # ConfigParser is not a new-style class, so inherit from object to fix super(). class BugwarriorConfigParser(ConfigParser, object): def getint(self, section, option): """ Accepts both integers and empty values. """ try: return super(BugwarriorConfigParser, self).getint(section, option) except ValueError: if self.get(section, option) == u'': return None else: raise ValueError( "{section}.{option} must be an integer or empty.".format( section=section, option=option)) class ServiceConfig(object): """ A service-aware wrapper for ConfigParser objects. """ def __init__(self, config_prefix, config_parser, service_target): self.config_prefix = config_prefix self.config_parser = config_parser self.service_target = service_target def __getattr__(self, name): """ Proxy undefined attributes/methods to ConfigParser object. """ return getattr(self.config_parser, name) def __contains__(self, key): """ Does service section specify this option? """ return self.config_parser.has_option( self.service_target, self._get_key(key)) def get(self, key, default=None, to_type=None): try: value = self.config_parser.get(self.service_target, self._get_key(key)) if to_type: return to_type(value) return value except: return default def _get_key(self, key): return '%s.%s' % (self.config_prefix, key) # This needs to be imported here and not above to avoid a circular-import. from bugwarrior.services import get_service bugwarrior-1.5.1/bugwarrior/docs/0000755000175000017500000000000013111575230020726 5ustar threebeanthreebean00000000000000bugwarrior-1.5.1/bugwarrior/docs/services.rst0000644000175000017500000000020512421224277023305 0ustar threebeanthreebean00000000000000Supported Services ================== Bugwarrior currently supports the following services: .. toctree:: :glob: services/* bugwarrior-1.5.1/bugwarrior/docs/services/0000755000175000017500000000000013111575230022551 5ustar threebeanthreebean00000000000000bugwarrior-1.5.1/bugwarrior/docs/services/teamlab.rst0000644000175000017500000000300313111574702024707 0ustar threebeanthreebean00000000000000Teamlab ======= You can import tasks from your Teamlab instance using the ``teamlab`` service name. Example Service --------------- Here's an example of a Teamlab target:: [my_issue_tracker] service = teamlab teamlab.hostname = teamlab.example.com teamlab.login = alice teamlab.password = secret teamlab.project_name = example_teamlab The above example is the minimum required to import issues from Teamlab. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options`. Provided UDA Fields ------------------- +---------------------------+---------------------------+---------------------------+ | Field Name | Description | Type | +===========================+===========================+===========================+ | ``teamlabid`` | ID | Text (string) | +---------------------------+---------------------------+---------------------------+ | ``teamlabprojectownerid`` | ProjectOwner ID | Text (string) | +---------------------------+---------------------------+---------------------------+ | ``teamlabtitle`` | Title | Text (string) | +---------------------------+---------------------------+---------------------------+ | ``teamlaburl`` | URL | Text (string) | +---------------------------+---------------------------+---------------------------+ bugwarrior-1.5.1/bugwarrior/docs/services/pagure.rst0000644000175000017500000000757512671046656024623 0ustar threebeanthreebean00000000000000Pagure ====== You can import tasks from your private or public `pagure `_ instance using the ``pagure`` service name. Example Service --------------- Here's an example of a Pagure target:: [my_issue_tracker] service = pagure pagure.tag = releng pagure.base_url = https://pagure.io The above example is the minimum required to import issues from Pagure. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options` or described in `Service Features`_ below. Note that **either** ``pagure.tag`` or ``pagure.repo`` is required. - ``pagure.tag`` offers a flexible way to import issues from many pagure repos. It will include issues from *every* repo on the pagure instance that is *tagged* with the specified tag. It is similar in usage to a github "organization". In the example above, the entry will pull issues from all "releng" pagure repos. - ``pagure.repo`` offers a simple way to import issues from a single pagure repo. Note -- no authentication tokens are needed to pull issues from pagure. Service Features ---------------- Include and Exclude Certain Repositories ++++++++++++++++++++++++++++++++++++++++ If you happen to be working with a large number of projects, you may want to pull issues from only a subset of your repositories. To do that, you can use the ``pagure.include_repos`` option. For example, if you would like to only pull-in issues from your ``project_foo`` and ``project_fox`` repositories, you could add this line to your service configuration:: pagure.tag = fedora-infra pagure.include_repos = project_foo,project_fox Alternatively, if you have a particularly noisy repository, you can instead choose to import all issues excepting it using the ``pagure.exclude_repos`` configuration option. In this example, ``noisy_repository`` is the repository you would *not* like issues created for:: pagure.tag = fedora-infra pagure.exclude_repos = noisy_repository Import Labels as Tags +++++++++++++++++++++ The Pagure issue tracker allows you to attach tags to issues; to use those pagure tags as taskwarrior tags, you can use the ``pagure.import_tags`` option:: pagure.import_tags = True Also, if you would like to control how these taskwarrior tags are created, you can specify a template used for converting the Pagure tag into a Taskwarrior tag. For example, to prefix all incoming labels with the string 'pagure_' (perhaps to differentiate them from any existing tags you might have), you could add the following configuration option:: pagure.label_template = pagure_{{label}} In addition to the context variable ``{{label}}``, you also have access to all fields on the Taskwarrior task if needed. .. note:: See :ref:`field_templates` for more details regarding how templates are processed. Provided UDA Fields ------------------- +-----------------------+---------------------+---------------------+ | Field Name | Description | Type | +=======================+=====================+=====================+ | ``paguredatecreated`` | Created | Date & Time | +-----------------------+---------------------+---------------------+ | ``pagurenumber`` | Issue/PR # | Numeric | +-----------------------+---------------------+---------------------+ | ``paguretitle`` | Title | Text (string) | +-----------------------+---------------------+---------------------+ | ``paguretype`` | Type | Text (string) | +-----------------------+---------------------+---------------------+ | ``pagureurl`` | URL | Text (string) | +-----------------------+---------------------+---------------------+ | ``pagurerepo`` | username/reponame | Text (string) | +-----------------------+---------------------+---------------------+ bugwarrior-1.5.1/bugwarrior/docs/services/trello.rst0000644000175000017500000001274613111574702024621 0ustar threebeanthreebean00000000000000Trello ====== You can import tasks from Trello cards using the ``trello`` service name. Options ------- .. describe:: trello.api_key Your Trello API key, available from https://trello.com/app-key .. describe:: trello.token Trello token, see below for how to get it. .. describe:: trello.include_boards The list of board to include. If omitted, bugwarrior will use all boards the authenticated user is a member of. This can be either the board ids of the board "short links". The latter is the easiest option as it is part of the board URL: in your browser, navigate to the board you want to pull cards from and look at the URL, it should be something like ``https://trello.com/b/xxxxxxxx/myboard``: copy the part between ``/b/`` and the next ``/`` in the ``trello.include_boards`` field. .. image:: pictures/trello_url.png :height: 1cm .. describe:: trello.include_lists If set, only pull cards from lists whose name is present in ``trello.include_lists``. .. describe:: trello.exclude_lists If set, skip cards from lists whose name is present in ``trello.exclude_lists``. .. describe:: trello.import_labels_as_tags A boolean that indicates whether the Trello labels should be imported as tags in taskwarrior. (Defaults to false.) .. describe:: trello.label_template Template used to convert Trello labels to taskwarrior tags. See :ref:`field_templates` for more details regarding how templates are processed. The default value is ``{{label|replace(' ', '_')}}``. Example Service --------------- Here's an example of a Trello target:: [my_project] service = trello trello.api_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx trello.token = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx The above example is the minimum required to import tasks from Trello. This will import every card from all the user's boards. Here's an example with more options:: [my_project] service = trello trello.api_key = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx trello.token = xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx trello.include_boards = AaBbCcDd, WwXxYyZz trello.include_lists = Todo, Doing trello.exclude_lists = Done trello.only_if_assigned = someuser trello.import_labels_as_tags = true In this case, ``bugwarrior`` will only import cards from the specified boards if they belong to the right lists.. Feel free to use any of the configuration options described in :ref:`common_configuration_options` or described in `Service Features`_ below. .. HINT: Getting your API key and access token To get your API key, go to https://trello.com/app-key and copy the given key (this is your ``trello.api_key``). Next, go to https://trello.com/1/connect?key=TRELLO_API_KEY&name=bugwarrior&response_type=token&scope=read,write&expiration=never replacing ``TRELLO_API_KEY`` by the key you got on the last step. Copy the given toke (this is your ``trello.token``). Service Features ---------------- Include and Exclude Certain Lists +++++++++++++++++++++++++++++++++ You may want to pull cards from only a subset of the open lists in your board. To do that, you can use the ``trello.include_lists`` and ``trello.exclude_lists`` options. For example, if you would like to only pull-in cards from your ``Todo`` and ``Doing`` lists, you could add this line to your service configuration:: trello.include_lists = Todo, Doing Import Labels as Tags +++++++++++++++++++++ Trello allows you to attach labels to cards; to use those labels as tags, you can use the ``trello.import_labels_as_tags`` option:: trello.import_labels_as_tags = True Also, if you would like to control how these labels are created, you can specify a template used for converting the trello label into a Taskwarrior tag. For example, to prefix all incoming labels with the string 'trello_' (perhaps to differentiate them from any existing tags you might have), you could add the following configuration option:: trello.label_template = trello_{{label}} In addition to the context variable ``{{label}}``, you also have access to all fields on the Taskwarrior task if needed. .. note:: See :ref:`field_templates` for more details regarding how templates are processed. The default value is ``{{label|upper|replace(' ', '_')}}``. Provided UDA Fields ------------------- +-----------------------+-----------------------+---------------------+ | Field Name | Description | Type | +=======================+=======================+=====================+ | ``trelloboard`` | Board name | Text (string) | +-----------------------+-----------------------+---------------------+ | ``trellocard`` | Card name | Text (string) | +-----------------------+-----------------------+---------------------+ | ``trellocardid`` | Card ID | Text (string) | +-----------------------+-----------------------+---------------------+ | ``trellolist`` | List name | Text (string) | +-----------------------+-----------------------+---------------------+ | ``trelloshortlink`` | Short Link | Text (string) | +-----------------------+-----------------------+---------------------+ | ``trelloshorturl`` | Short URL | Text (string) | +-----------------------+-----------------------+---------------------+ | ``trellourl`` | Full URL | Text (string) | +-----------------------+-----------------------+---------------------+ bugwarrior-1.5.1/bugwarrior/docs/services/jira.rst0000644000175000017500000001002613111574702024232 0ustar threebeanthreebean00000000000000Jira ==== You can import tasks from your Jira instance using the ``jira`` service name. Additional Requirements ----------------------- Install the following package using ``pip``: * ``jira`` Example Service --------------- Here's an example of a jira project:: [my_issue_tracker] service = jira jira.base_uri = https://bug.tasktools.org jira.username = ralph jira.password = OMG_LULZ .. note:: The ``base_uri`` must not have a trailing slash. The above example is the minimum required to import issues from Jira. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options` or described in `Service Features`_ below. Service Features ---------------- Specify the Query to Use for Gathering Issues +++++++++++++++++++++++++++++++++++++++++++++ By default, the JIRA plugin will include any issues that are assigned to you but do not yet have a resolution set, but you can fine-tune the query used for gathering issues by setting the ``jira.query`` parameter. For example, to select issues assigned to 'ralph' having a status that is not 'closed' and is not 'resolved', you could add the following configuration option:: jira.query = assignee = ralph and status != closed and status != resolved This query needs to be modified accordingly to the literal values of your Jira instance; if the name contains any character, just put it in quotes, e.g. jira.query = assignee = 'firstname.lastname' and status != Closed and status != Resolved and status != Done Jira v4 Support +++++++++++++++ If you happen to be using a very old version of Jira, add the following configuration option to your service configuration:: jira.version = 4 Do Not Verify SSL Certificate +++++++++++++++++++++++++++++ If you want to ignore verifying the SSL certificate, set:: jira.verify_ssl = False Import Labels and Sprints as Tags +++++++++++++++++++++++++++++++++ The Jira issue tracker allows you to attach labels to issues; to use those labels as tags, you can use the ``jira.import_labels_as_tags`` option:: jira.import_labels_as_tags = True You can also import the names of any sprints associated with an issue as tags, by setting the ``jira.import_sprints_as_tags`` option:: jira.import_sprints_as_tags = True If you would like to control how these labels are created, you can specify a template used for converting the Jira label into a Taskwarrior tag. For example, to prefix all incoming labels with the string 'jira_' (perhaps to differentiate them from any existing tags you might have), you could add the following configuration option:: jira.label_template = jira_{{label}} In addition to the context variable ``{{label}}``, you also have access to all fields on the Taskwarrior task if needed. .. note:: See :ref:`field_templates` for more details regarding how templates are processed. Kerberos authentication +++++++++++++++++++++++ If the ``password`` is specified as ``@kerberos``, the service plugin will try to authenticate against server with kerberos. A ticket must be already present on the client (created by running ``kinit`` or any other method). Provided UDA Fields ------------------- +---------------------+---------------------+---------------------+ | Field Name | Description | Type | +=====================+=====================+=====================+ | ``jiradescription`` | Description | Text (string) | +---------------------+---------------------+---------------------+ | ``jiraid`` | Issue ID | Text (string) | +---------------------+---------------------+---------------------+ | ``jirasummary`` | Summary | Text (string) | +---------------------+---------------------+---------------------+ | ``jiraurl`` | URL | Text (string) | +---------------------+---------------------+---------------------+ | ``jiraestimate`` | Estimate | Decimal (numeric) | +---------------------+---------------------+---------------------+ bugwarrior-1.5.1/bugwarrior/docs/services/redmine.rst0000644000175000017500000000240213111574702024727 0ustar threebeanthreebean00000000000000Redmine ======= You can import tasks from your Redmine instance using the ``redmine`` service name. Only first 100 issues are imported at the moment. Example Service --------------- Here's an example of a Redmine target:: [my_issue_tracker] service = redmine redmine.url = http://redmine.example.org/ redmine.key = c0c4c014cafebabe redmine.user_id = 7 redmine.project_name = redmine redmine.issue_limit = 100 You can also feel free to use any of the configuration options described in :ref:`common_configuration_options`. There are also `redmine.login`/`redmine.password` settings if your instance is behind basic auth. Provided UDA Fields ------------------- +--------------------+--------------------+--------------------+ | Field Name | Description | Type | +====================+====================+====================+ | ``redmineid`` | ID | Text (string) | +--------------------+--------------------+--------------------+ | ``redminesubject`` | Subject | Text (string) | +--------------------+--------------------+--------------------+ | ``redmineurl`` | URL | Text (string) | +--------------------+--------------------+--------------------+ bugwarrior-1.5.1/bugwarrior/docs/services/phabricator.rst0000644000175000017500000000552613111574702025614 0ustar threebeanthreebean00000000000000Phabricator =========== You can import tasks from your Phabricator instance using the ``phabricator`` service name. Additional Requirements ----------------------- Install the following package using ``pip``: * ``phabricator`` Example Service --------------- Here's an example of a Phabricator target:: [my_issue_tracker] service = phabricator .. note:: Although this may not look like enough information for us to gather information from Phabricator, but credentials will be gathered from the user's ``~/.arcrc``. The above example is the minimum required to import issues from Phabricator. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options`. Service Features ---------------- If you have dozens of users and projects, you might want to pull the tasks and code review requests only for the specific ones. If you want to show only the tasks related to a specific user, you just need to add its PHID to the service configuration like this:: phabricator.user_phids = PHID-USER-ab12c3defghi45jkl678 If you want to show only the tasks and diffs related to a specific project or a repository, just add their PHIDs to the service configuration:: phabricator.project_phids = PHID-PROJ-ab12c3defghi45jkl678,PHID-REPO-ab12c3defghi45jkl678 Both ``phabricator.user_phids`` and ``phabricator.project_phids`` accept a comma-separated (no spaces) list of PHIDs. If you specify both, you will get tasks and diffs that match one **or** the other. When working on a Phabricator installations with a huge number of users or projects, it is recommended that you specify ``phabricator.user_phids`` and/or ``phabricator.project_phids``, as the Phabricator API may return a timeout for a query with too many results. If you do not know PHID of a user, project or repository, you can find it out by querying Phabricator Conduit (``https://YOUR_PHABRICATOR_HOST/conduit/``) -- the methods which return the needed info are ``user.query``, ``project.query`` and ``repository.query`` respectively. Provided UDA Fields ------------------- +----------------------+----------------------+----------------------+ | Field Name | Description | Type | +======================+======================+======================+ | ``phabricatorid`` | Object | Text (string) | +----------------------+----------------------+----------------------+ | ``phabricatortitle`` | Title | Text (string) | +----------------------+----------------------+----------------------+ | ``phabricatortype`` | Type | Text (string) | +----------------------+----------------------+----------------------+ | ``phabricatorurl`` | URL | Text (string) | +----------------------+----------------------+----------------------+ bugwarrior-1.5.1/bugwarrior/docs/services/youtrack.rst0000644000175000017500000000642313111574702025154 0ustar threebeanthreebean00000000000000YouTrack ======== You can import tasks from your YouTrack instance using the ``youtrack`` service name. Example Service --------------- Here's an example of a YouTrack target:: [my_issue_tracker] service = youtrack youtrack.host = youtrack.example.com youtrack.login = turing youtrack.password = 3n1Gm@ The above example is the minimum required to import issues from YouTrack. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options` or described in `Service Features`_ below. Service Features ---------------- Customize the YouTrack Connection +++++++++++++++++++++++++++++++++ The ``youtrack.host`` field is used to construct a URL for the YouTrack server. It defaults to a secure connection scheme (HTTPS) on the standard port (443). To connect on a different port, set:: youtrack.port = 8443 If your YouTrack instance is only available over HTTP, set:: youtrack.use_https = False If you want to ignore verifying the SSL certificate, set:: youtrack.verify_ssl = False Specify the Query to Use for Gathering Issues +++++++++++++++++++++++++++++++++++++++++++++ The default option selects unresolved issues assigned to the login user:: youtrack.query = for:me #Unresolved Reference the `YouTrack Search Query Grammar `_ for additional examples. Queries are capped at 100 max results by default, but may be adjusted to meet your needs:: youtrack.query_limit = 100 Import Issue Tags +++++++++++++++++ The YouTrack issue tracker allows you to tag issues. To apply these tags in Taskwarrior, set:: youtrack.import_tags = True If you would like to control how these tags are formatted, you can specify a template used for converting the YouTrack tag into a Taskwarrior tag. For example, to prefix all incoming tags with the string 'yt\_' (perhaps to differentiate them from any existing tags you might have), you could add the following configuration option:: youtrack.tag_template = yt_{{tag|lower}} In addition to the context variable ``{{tag}}``, you also have access to all fields on the Taskwarrior task if needed. .. note:: See :ref:`field_templates` for more details regarding how templates are processed. Provided UDA Fields ------------------- +---------------------------+----------------------+---------------------+ | Field Name | Description | Type | +===========================+======================+=====================+ | ``youtrackissue`` | PROJECT-ISSUE# | Text (string) | +---------------------------+----------------------+---------------------+ | ``youtracksummary`` | Summary | Text (string) | +---------------------------+----------------------+---------------------+ | ``youtrackurl`` | URL | Text (string) | +---------------------------+----------------------+---------------------+ | ``youtrackproject`` | Project short name | Text (string) | +---------------------------+----------------------+---------------------+ | ``youtracknumber`` | Project issue number | Numeric | +---------------------------+----------------------+---------------------+ bugwarrior-1.5.1/bugwarrior/docs/services/bugzilla.rst0000644000175000017500000000702013111574702025116 0ustar threebeanthreebean00000000000000Bugzilla ========================= You can import tasks from your Bz instance using the ``bugzilla`` service name. Additional Dependencies ----------------------- Install packages needed for Bugzilla support with:: pip install bugwarrior[bugzilla] Example Service --------------- Here's an example of a bugzilla target. This will scrape every ticket that: 1. Is not closed and 2. rbean@redhat.com is either the owner, reporter or is cc'd on the issue. Bugzilla instances can be quite different from one another so use this with caution and please report bugs so we can make bugwarrior support more robust! :: [my_issue_tracker] service = bugzilla bugzilla.base_uri = bugzilla.redhat.com bugzilla.username = rbean@redhat.com bugzilla.password = OMG_LULZ Alternately, if you are using a version of python-bugzilla newer than 2.1.0, you can specify an API key instead of a password. Note that the username is still required in this case, in order to identify bugs belonging to you. :: bugzilla.api_key = 4f4d475f4c554c5a4f4d475f4c554c5a The above example is the minimum required to import issues from Bugzilla. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options`. Note, however, that the filtering options, including ``only_if_assigned`` and ``also_unassigned``, do not work There is an option to ignore bugs that you are only cc'd on:: bugzilla.ignore_cc = True But this will continue to include bugs that you reported, regardless of whether they are assigned to you. If your bugzilla "actionable" bugs only include ON_QA, FAILS_QA, PASSES_QA, and POST:: bugzilla.open_statuses = ON_QA,FAILS_QA,PASSES_QA,POST This won't create tasks for bugs in other states. The default open statuses: "NEW,ASSIGNED,NEEDINFO,ON_DEV,MODIFIED,POST,REOPENED,ON_QA,FAILS_QA,PASSES_QA" If you're on a more recent Bugzilla install, the NEEDINFO status no longer exists, and has been replaced by the "needinfo?" flag. Set "bugzilla.include_needinfos" to "True" to have taskwarrior also add bugs where information is requested of you. The "bugzillaneedinfo" UDA will be filled in with the date the needinfo was set. To see all your needinfo bugs, you can use "task bugzillaneedinfo.any: list". If the filtering options are not sufficient to find the set of bugs you'd like, you can tell Bugwarrior exactly which bugs to sync by pasting a full query URL from your browser into the ``bugzilla.query_url`` option:: bugzilla.query_url = https://bugzilla.mozilla.org/query.cgi?bug_status=ASSIGNED&email1=myname%40mozilla.com&emailassigned_to1=1&emailtype1=exact Provided UDA Fields ------------------- +----------------------+---------------------+---------------------+ | Field Name | Description | Type | +======================+=====================+=====================+ | ``bugzillasummary`` | Summary | Text (string) | +----------------------+---------------------+---------------------+ | ``bugzillaurl`` | URL | Text (string) | +----------------------+---------------------+---------------------+ | ``bugzillabugid`` | Bug ID | Numeric (integer) | +----------------------+---------------------+---------------------+ | ``bugzillastatus`` | Bugzilla Status | Text (string) | +----------------------+---------------------+---------------------+ | ``bugzillaneedinfo`` | Needinfo | Date | +----------------------+---------------------+---------------------+ bugwarrior-1.5.1/bugwarrior/docs/services/gitlab.rst0000644000175000017500000001401313111574702024547 0ustar threebeanthreebean00000000000000Gitlab ====== You can import tasks from your Gitlab instance using the ``gitlab`` service name. Example Service --------------- Here's an example of a Gitlab target:: [my_issue_tracker] service = gitlab gitlab.login = ralphbean gitlab.token = OMG_LULZ gitlab.host = gitlab.com The above example is the minimum required to import issues from Gitlab. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options` or described in `Service Features`_ below. The ``gitlab.token`` is your private API token. Service Features ---------------- Include and Exclude Certain Repositories ++++++++++++++++++++++++++++++++++++++++ If you happen to be working with a large number of projects, you may want to pull issues from only a subset of your repositories. To do that, you can use the ``gitlab.include_repos`` option. For example, if you would like to only pull-in issues from your own ``project_foo`` and team ``bar``'s ``project_fox`` repositories, you could add this line to your service configuration (replacing ``me`` by your own login):: gitlab.include_repos = me/project_foo, bar/project_fox Alternatively, if you have a particularly noisy repository, you can instead choose to import all issues excepting it using the ``gitlab.exclude_repos`` configuration option. In this example, ``noisy/repository`` is the repository you would *not* like issues created for:: gitlab.exclude_repos = noisy/repository .. hint:: If you omit the repository's namespace, bugwarrior will automatically add your login as namespace. E.g. the following are equivalent:: gitlab.login = foo gitlab.include_repos = bar and:: gitlab.login = foo gitlab.include_repos = foo/bar Import Labels as Tags +++++++++++++++++++++ The gitlab issue tracker allows you to attach labels to issues; to use those labels as tags, you can use the ``gitlab.import_labels_as_tags`` option:: gitlab.import_labels_as_tags = True Also, if you would like to control how these labels are created, you can specify a template used for converting the gitlab label into a Taskwarrior tag. For example, to prefix all incoming labels with the string 'gitlab_' (perhaps to differentiate them from any existing tags you might have), you could add the following configuration option:: gitlab.label_template = gitlab_{{label}} In addition to the context variable ``{{label}}``, you also have access to all fields on the Taskwarrior task if needed. .. note:: See :ref:`field_templates` for more details regarding how templates are processed. Include Merge Requests ++++++++++++++++++++++ Although you can filter issues using :ref:`common_configuration_options`, merge requests are not filtered by default. You can filter merge requests by adding the following configuration option:: gitlab.filter_merge_requests = True Include Todo Items ++++++++++++++++++ By default todo items are not included. You may include them by adding the following configuration option:: gitlab.include_todos = True If todo items are included, by default, todo items for all projects are included. To only fetch todo items for projects which are being fetched, you may set:: gitlab.include_all_todos = False Include Only One Author +++++++++++++++++++++++ If you would like to only pull issues and MRs that you've authored, you may set:: gitlab.only_if_author = myusername Use HTTP ++++++++ If your Gitlab instance is only available over HTTP, set:: gitlab.use_https = False Do Not Verify SSL Certificate +++++++++++++++++++++++++++++ If you want to ignore verifying the SSL certificate, set:: gitlab.verify_ssl = False Provided UDA Fields ------------------- +-----------------------+-----------------------+---------------------+ | Field Name | Description | Type | +=======================+=======================+=====================+ | ``gitlabdescription`` | Description | Text (string) | +-----------------------+-----------------------+---------------------+ | ``gitlabcreatedon`` | Created | Date & Time | +-----------------------+-----------------------+---------------------+ | ``gitlabmilestone`` | Milestone | Text (string) | +-----------------------+-----------------------+---------------------+ | ``gitlabnumber`` | Issue/MR # | Numeric | +-----------------------+-----------------------+---------------------+ | ``gitlabtitle`` | Title | Text (string) | +-----------------------+-----------------------+---------------------+ | ``gitlabtype`` | Type | Text (string) | +-----------------------+-----------------------+---------------------+ | ``gitlabupdatedat`` | Updated | Date & Time | +-----------------------+-----------------------+---------------------+ | ``gitlabduedate`` | Due Date | Date | +-----------------------+-----------------------+---------------------+ | ``gitlaburl`` | URL | Text (string) | +-----------------------+-----------------------+---------------------+ | ``gitlabrepo`` | username/reponame | Text (string) | +-----------------------+-----------------------+---------------------+ | ``gitlabupvotes`` | Number of upvotes | Numeric | +-----------------------+-----------------------+---------------------+ | ``gitlabdownvotes`` | Number of downvotes | Numeric | +-----------------------+-----------------------+---------------------+ | ``gitlabwip`` | Work-in-Progress flag | Numeric | +-----------------------+-----------------------+---------------------+ | ``gitlabauthor`` | Issue/MR author | Text (string) | +-----------------------+-----------------------+---------------------+ | ``gitlabassignee`` | Issue/MR assignee | Text (string) | +-----------------------+-----------------------+---------------------+ bugwarrior-1.5.1/bugwarrior/docs/services/github.rst0000644000175000017500000001433113111574702024572 0ustar threebeanthreebean00000000000000Github ====== You can import tasks from your Github instance using the ``github`` service name. Example Service --------------- Here's an example of a Github target:: [my_issue_tracker] service = github github.login = ralphbean github.password = OMG_LULZ github.username = ralphbean The above example is the minimum required to import issues from Github. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options` or described in `Service Features`_ below. ``github.login`` is used to specify what account bugwarrior should use to login to github, combined with ``github.password``. If two-factor authentication is used, ``github.token`` must be given rather than ``github.password``. To get a token, go to the "Personal access tokens" section of your profile settings. Only the ``public_repo`` scope is required, but access to private repos can be gained with ``repo`` as well. Service Features ---------------- Repo Owner ++++++++++ ``github.username`` indicates which repositories should be scraped. For instance, I always have ``github.login`` set to ralphbean (my account). But I have some targets with ``github.username`` pointed at organizations or other users to watch issues there. This parameter is required unless ``github.query`` is provided. Include and Exclude Certain Repositories ++++++++++++++++++++++++++++++++++++++++ By default, issues from all repos belonging to ``github.username`` are included. To turn this off, set:: github.include_user_repos = False If you happen to be working with a large number of projects, you may want to pull issues from only a subset of your repositories. To do that, you can use the ``github.include_repos`` option. For example, if you would like to only pull-in issues from your ``project_foo`` and ``project_fox`` repositories, you could add this line to your service configuration:: github.include_repos = project_foo,project_fox Alternatively, if you have a particularly noisy repository, you can instead choose to import all issues excepting it using the ``github.exclude_repos`` configuration option. In this example, ``noisy_repository`` is the repository you would *not* like issues created for:: github.exclude_repos = noisy_repository Import Labels as Tags +++++++++++++++++++++ The Github issue tracker allows you to attach labels to issues; to use those labels as tags, you can use the ``github.import_labels_as_tags`` option:: github.import_labels_as_tags = True Also, if you would like to control how these labels are created, you can specify a template used for converting the Github label into a Taskwarrior tag. For example, to prefix all incoming labels with the string 'github_' (perhaps to differentiate them from any existing tags you might have), you could add the following configuration option:: github.label_template = github_{{label}} In addition to the context variable ``{{label}}``, you also have access to all fields on the Taskwarrior task if needed. .. note:: See :ref:`field_templates` for more details regarding how templates are processed. Filter Pull Requests ++++++++++++++++++++ Although you can filter issues using :ref:`common_configuration_options`, pull requests are not filtered by default. You can filter pull requests by adding the following configuration option:: github.filter_pull_requests = True Get involved issues +++++++++++++++++++ By default, bugwarrior pulls all issues across owned and member repositories assigned to the authenticated user. To disable this behavior, use:: github.include_user_issues = False Instead of fetching issues and pull requests based on ``{{username}}``'s owned repositories, you may instead get those that ``{{username}}`` is involved in. This includes all issues and pull requests where the user is the author, the assignee, mentioned in, or has commented on. To do so, add the following configuration option:: github.involved_issues = True Queries +++++++ If you want to write your own github query, as described at https://help.github.com/articles/searching-issues/:: github.query = assignee:octocat is:open Note that this search covers both issues and pull requests, which github treats as a special kind of issue. To disable the pre-defined queries described above and synchronize only the issues matched by the query, set:: github.include_user_issues = False github.include_user_repos = False GitHub Enterprise Instance ++++++++++++++++++++++++++ If you're using GitHub Enterprise, the on-premises version of GitHub, you can point bugwarrior to it with the ``github.host`` configuration option. E.g.:: github.host = github.acme.biz Provided UDA Fields ------------------- +---------------------+---------------------+---------------------+ | Field Name | Description | Type | +=====================+=====================+=====================+ | ``githubbody`` | Body | Text (string) | +---------------------+---------------------+---------------------+ | ``githubcreatedon`` | Created | Date & Time | +---------------------+---------------------+---------------------+ | ``githubmilestone`` | Milestone | Text (string) | +---------------------+---------------------+---------------------+ | ``githubnumber`` | Issue/PR # | Numeric | +---------------------+---------------------+---------------------+ | ``githubtitle`` | Title | Text (string) | +---------------------+---------------------+---------------------+ | ``githubtype`` | Type | Text (string) | +---------------------+---------------------+---------------------+ | ``githubupdatedat`` | Updated | Date & Time | +---------------------+---------------------+---------------------+ | ``githuburl`` | URL | Text (string) | +---------------------+---------------------+---------------------+ | ``githubrepo`` | username/reponame | Text (string) | +---------------------+---------------------+---------------------+ | ``githubuser`` | Author of issue/PR | Text (string) | +---------------------+---------------------+---------------------+ bugwarrior-1.5.1/bugwarrior/docs/services/taiga.rst0000644000175000017500000000255413111574702024401 0ustar threebeanthreebean00000000000000Taiga ===== You can import tasks from a Taiga instance using the ``taiga`` service name. Example Service --------------- Here's an example of a taiga project:: [my_issue_tracker] service = taiga taiga.base_uri = http://taiga.fedorainfracloud.org taiga.auth_token = ayJ1c4VyX2F1dGhlbnQpY2F0aW9uX2lmIjo1fQ:2a2LPT:qscLbfQC_jyejQsICET5KgYNPLM The above example is the minimum required to import issues from Taiga. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options`. Provided UDA Fields ------------------- +---------------------+---------------------+---------------------+ | Field Name | Description | Type | +=====================+=====================+=====================+ | ``taigaid`` | Issue ID | Text (string) | +---------------------+---------------------+---------------------+ | ``taigasummary`` | Summary | Text (string) | +---------------------+---------------------+---------------------+ | ``taigaurl`` | URL | Text (string) | +---------------------+---------------------+---------------------+ The Taiga service provides a limited set of UDAs. If you have need for some other values not present here, please file a request (there's lots of metadata in there that we could expose). bugwarrior-1.5.1/bugwarrior/docs/services/bts.rst0000644000175000017500000001140613111574702024100 0ustar threebeanthreebean00000000000000Debian Bug Tracking System (BTS) ================================ You can import tasks from the Debian Bug Tracking System (BTS) using the ``bts`` service name. Debian's bugs are public and no authentication information is required by bugwarrior for this service. Additional Requirements ----------------------- You will need to install the following additional packages via ``pip``: * ``PySimpleSOAP`` * ``python-debianbts`` .. note:: If you have installed the Debian package for bugwarrior, this dependency will already be satisfied. Example Service --------------- Here's an example of a Debian BTS target:: [debian_bts] service = bts bts.email = username@debian.org The above example is the minimum required to import issues from the Debian BTS. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options` or described in `Service Features`_ below. Service Features ---------------- Include all bugs for packages +++++++++++++++++++++++++++++ If you would like more bugs than just those you are the owner of, you can specify the ``bts.packages`` option. For example if you wanted to include bugs on the ``hello`` package, you can add this line to your service configuration:: bts.packages = hello More packages can be specified seperated by commas. Ultimate Debian Database (UDD) Bugs Search ++++++++++++++++++++++++++++++++++++++++++ If you maintain a large number of packages and you wish to include bugs from all packages where you are listed as a Maintainer or an Uploader in the Debian archive, you can enable the use of the `UDD Bugs Search `_. This will peform a search and include the bugs from the result. To enable this feature, you can add this line to your service configuration:: bts.udd = True Excluding bugs marked pending +++++++++++++++++++++++++++++ Debian bugs are not considered closed until the fixed package is present in the Debian archive. Bugs do cease to be outstanding tasks however as soon as you have completed the work, and so it can be useful to exclude bugs that you have marked with the pending tag in the BTS. This is the default behaviour, but if you feel you would like to include bugs that are marked as pending in the BTS, you can disable this by adding this line to your service configuration:: bts.ignore_pending = False Excluding sponsored and NMU'd packages ++++++++++++++++++++++++++++++++++++++ If you maintain an even larger number of packages, you may wish to exclude some packages. You can exclude packages that you have sponsored or have uploaded as a non-maintainer upload or team upload by adding the following line to your service configuration:: bts.udd_ignore_sponsor = True .. note:: This will only affect the bugs returned by the UDD bugs search service and will not exclude bugs that are discovered due to ownership or due to packages explicitly specified in the service configuration. Excluding packages explicitly +++++++++++++++++++++++++++++ If you would like to exclude a particularly noisy package, that is perhaps team maintained, or a package that you have orphaned and no longer have interest in but are still listed as Maintainer or Uploader in stable suites, you can explicitly ignore bugs based on their binary or source package names. To do this add one of the following lines to your service configuration:: bts.ignore_pkg = hello,anarchism bts.ignore_src = linux .. note:: The ``src:`` prefix that is commonly seen in the Debian BTS interface is not required when specifying source packages to exclude. Provided UDA Fields ------------------- +---------------------+---------------------+---------------------+ | Field Name | Description | Type | +=====================+=====================+=====================+ | ``btsnumber`` | Bug Number | Text (string) | +---------------------+---------------------+---------------------+ | ``btsurl`` | bugs.d.o URL | Text (string) | +---------------------+---------------------+---------------------+ | ``btssubject`` | Subject | Text (string) | +---------------------+---------------------+---------------------+ | ``btssource`` | Source Package | Text (string) | +---------------------+---------------------+---------------------+ | ``btspackage`` | Binary Package | Text (string) | +---------------------+---------------------+---------------------+ | ``btsforwarded`` | Forwarded URL | Text (string) | +---------------------+---------------------+---------------------+ | ``btsstatus`` | Status | Text (string) | +---------------------+---------------------+---------------------+ bugwarrior-1.5.1/bugwarrior/docs/services/gerrit.rst0000644000175000017500000000324113111574702024602 0ustar threebeanthreebean00000000000000Gerrit ====== You can import code reviews from a Gerrit instance using the ``gerrit`` service name. Example Service --------------- Here's an example of a gerrit project:: [my_issue_tracker] service = gerrit gerrit.base_uri = https://yourhomebase.xyz/gerrit/ gerrit.username = your_username gerrit.password = your_http_digest_password The above example is the minimum required to import issues from Gerrit. **Note** that the password is typically not your normal login password. Go to the "HTTP Password" section in your account settings to generate/retrieve this password. You can also pass an optional ``gerrit.ssl_ca_path`` option which will use an alternative certificate authority to verify the connection. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options`. Provided UDA Fields ------------------- +---------------------+---------------------+---------------------+ | Field Name | Description | Type | +=====================+=====================+=====================+ | ``gerritid`` | Issue ID | Text (string) | +---------------------+---------------------+---------------------+ | ``gerritsummary`` | Summary | Text (string) | +---------------------+---------------------+---------------------+ | ``gerriturl`` | URL | Text (string) | +---------------------+---------------------+---------------------+ The Gerrit service provides a limited set of UDAs. If you have need for some other values not present here, please file a request (there's lots of metadata in there that we could expose). bugwarrior-1.5.1/bugwarrior/docs/services/bitbucket.rst0000644000175000017500000000656313111574702025274 0ustar threebeanthreebean00000000000000Bitbucket ========= You can import tasks from your Bitbucket instance using the ``bitbucket`` service name. Example Service --------------- Here's an example of a Bitbucket target:: [my_issue_tracker] service = bitbucket bitbucket.username = ralphbean bitbucket.login = ralphbean bitbucket.password = mypassword The above example is the minimum required to import issues from Bitbucket. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options`. Note that both ``bitbucket.username`` and ``bitbucket.login`` are required and can be set to different values. ``bitbucket.login`` is used to specify what account bugwarrior should use to login to bitbucket. ``bitbucket.username`` indicates which repositories should be scraped. For instance, I always have ``bitbucket.login`` set to ralphbean (my account). But I have some targets with ``bitbucket.username`` pointed at organizations or other users to watch issues there. As an alternative to password authentication, there is OAuth. To get a key and secret, go to the "OAuth" section of your profile settings and click "Add consumer". Set the "Callback URL" to ``https://localhost/`` and set the appropriate permissions. Then assign your consumer's credentials to ``bitbucket.key`` and ``bitbucket.secret``. Note that you will have to provide a password (only) the first time you pull, so you may want to set ``bitbucket.password = @oracle:ask_password`` and run ``bugwarrior-pull --interactive`` on your next pull. Service Features ---------------- Include and Exclude Certain Repositories ++++++++++++++++++++++++++++++++++++++++ If you happen to be working with a large number of projects, you may want to pull issues from only a subset of your repositories. To do that, you can use the ``bitbucket.include_repos`` option. For example, if you would like to only pull-in issues from your ``project_foo`` and ``project_fox`` repositories, you could add this line to your service configuration:: bitbucket.include_repos = project_foo,project_fox Alternatively, if you have a particularly noisy repository, you can instead choose to import all issues excepting it using the ``bitbucket.exclude_repos`` configuration option. In this example, ``noisy_repository`` is the repository you would *not* like issues created for:: bitbucket.exclude_repos = noisy_repository Please note that the API returns all lowercase names regardless of the case of the repository in the web interface. Filter Merge Requests +++++++++++++++++++++ Although you can filter issues using :ref:`common_configuration_options`, pull requests are not filtered by default. You can filter pull requests by adding the following configuration option:: bitbucket.filter_merge_requests = True Provided UDA Fields ------------------- +--------------------+--------------------+--------------------+ | Field Name | Description | Type | +====================+====================+====================+ | ``bitbucketid`` | Issue ID | Text (string) | +--------------------+--------------------+--------------------+ | ``bitbuckettitle`` | Title | Text (string) | +--------------------+--------------------+--------------------+ | ``bitbucketurl`` | URL | Text (string) | +--------------------+--------------------+--------------------+ bugwarrior-1.5.1/bugwarrior/docs/services/activecollab2.rst0000644000175000017500000000570112421224277026025 0ustar threebeanthreebean00000000000000.. _activecollab2: ActiveCollab 2 ============== You can import tasks from your ActiveCollab2 instance using the ``activecollab2`` service name. Instructions ------------ You can obtain your user ID and API url by logging into ActiveCollab and clicking on "Profile" and then "API Settings". When on that page, look at the URL. The integer that appears after "/user/" is your user ID. Projects should be entered in a comma-separated list, with the project id as the key and the name you'd like to use for the project in Taskwarrior entered as the value. For example, if the project ID is 8 and the project's name in ActiveCollab is "Amazing Website" then you might enter 8:amazing_website Note that due to limitations in the ActiveCollab API, there is no simple way to get a list of all tasks you are responsible for in AC. Instead you need to look at each ticket that you are subscribed to and check to see if your user ID is responsible for the ticket/task. What this means is that if you have 5 projects you want to query and each project has 20 tickets, you'll make 100 API requests each time you run ``bugwarrior-pull``. Example Service --------------- Here's an example of an activecollab2 target. Note that this will only work with ActiveCollab 2.x - see above for 3.x and greater. :: [my_bug_tracker] services = activecollab2 activecollab2.url = http://ac.example.org/api.php activecollab2.key = your-api-key activecollab2.user_id = 15 activecollab2.projects = 1:first_project, 5:another_project The above example is the minimum required to import issues from ActiveCollab 2. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options`. Provided UDA Fields ------------------- +--------------------+--------------------+--------------------+ | Field Name | Description | Type | +====================+====================+====================+ | ``ac2body`` | Body | Text (string) | +--------------------+--------------------+--------------------+ | ``ac2createdbyid`` | Created By | Text (string) | +--------------------+--------------------+--------------------+ | ``ac2createdon`` | Created On | Date & Time | +--------------------+--------------------+--------------------+ | ``ac2name`` | Name | Text (string) | +--------------------+--------------------+--------------------+ | ``ac2permalink`` | Permalink | Text (string) | +--------------------+--------------------+--------------------+ | ``ac2projectid`` | Project ID | Text (string) | +--------------------+--------------------+--------------------+ | ``ac2ticketid`` | Ticket ID | Text (string) | +--------------------+--------------------+--------------------+ | ``ac2type`` | Task Type | Text (string) | +--------------------+--------------------+--------------------+ bugwarrior-1.5.1/bugwarrior/docs/services/activecollab.rst0000644000175000017500000000616412470513341025744 0ustar threebeanthreebean00000000000000.. _activecollab4: ActiveCollab 4 ============== You can import tasks from your activeCollab 4.x instance using the ``activecollab`` service name. Additional Requirements ----------------------- Install the following packages using ``pip``: * ``pypandoc`` * ``pyac`` Instructions ------------ Obtain your user ID and API url by logging in, clicking on your avatar on the lower left-hand of the page. When on that page, look at the URL. The number that appears after "/user/" is your user ID. On the same page, go to Options and API Subscriptions. Generate a read-only API key and add that to your bugwarriorrc file. Bugwarrior will gather tasks and subtasks returned from the `my-tasks` API call. Additional API calls will be made to gather comments associated with each task. .. note:: Use of the ActiveCollab service requires that the following additional python modules be installed. - `pypandoc `_ - `pyac `_ Example Service --------------- Here's an example of an activecollab target. This is only valid for activeCollab 4.x and greater, see :ref:`activecollab2` for activeCollab2.x. :: [my_bug_tracker] service = activecollab activecollab.url = https://ac.example.org/api.php activecollab.key = your-api-key activecollab.user_id = 15 The above example is the minimum required to import issues from ActiveCollab 4. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options`. Provided UDA Fields ------------------- +---------------------+-----------------+----------------+ | Field Name | Description | Type | +=====================+=================+================+ | ``acbody`` | Body | Text (string) | +---------------------+-----------------+----------------+ | ``accreatedbyname`` | Created By Name | Text (string) | +---------------------+-----------------+----------------+ | ``accreatedon`` | Created On | Date & Time | +---------------------+-----------------+----------------+ | ``acid`` | ID | Text (string) | +---------------------+-----------------+----------------+ | ``acname`` | Name | Text (string) | +---------------------+-----------------+----------------+ | ``acpermalink`` | Permalink | Text (string) | +---------------------+-----------------+----------------+ | ``acprojectid`` | Project ID | Text (string) | +---------------------+-----------------+----------------+ | ``actaskid`` | Task ID | Text (string) | +---------------------+-----------------+----------------+ | ``actype`` | Task Type | Text (string) | +---------------------+-----------------+----------------+ | ``acestimatedtime`` | Estimated Time | Text (numeric) | +---------------------+-----------------+----------------+ | ``actrackedtime`` | Tracked Time | Text (numeric) | +---------------------+-----------------+----------------+ | ``acmilestone`` | Milestone | Text (string) | +---------------------+-----------------+----------------+ bugwarrior-1.5.1/bugwarrior/docs/services/trac.rst0000644000175000017500000000340113111574702024235 0ustar threebeanthreebean00000000000000Trac ==== You can import tasks from your Trac instance using the ``trac`` service name. Additional Dependencies ----------------------- Install packages needed for Trac support with:: pip install bugwarrior[trac] Example Service --------------- Here's an example of a Trac target:: [my_issue_tracker] service = trac trac.base_uri = fedorahosted.org/moksha trac.scheme = https trac.project_template = moksha.{{traccomponent|lower}} By default, this service uses the XML-RPC Trac plugin, which must be installed on the Trac instance. If this is not available, the service can use Trac's built-in CSV support, but in this mode it cannot add annotations based on ticket comments. To enable this mode, add ``trac.no_xmlrpc = true``. If your trac instance requires authentication to perform the query, add:: trac.username = ralph trac.password = OMG_LULZ The above example is the minimum required to import issues from Trac. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options`. Service Features ---------------- Provided UDA Fields ------------------- +-------------------+-----------------+-----------------+ | Field Name | Description | Type | +===================+=================+=================+ | ``tracnumber`` | Number | Text (string) | +-------------------+-----------------+-----------------+ | ``tracsummary`` | Summary | Text (string) | +-------------------+-----------------+-----------------+ | ``tracurl`` | URL | Text (string) | +-------------------+-----------------+-----------------+ | ``traccomponent`` | Component | Text (string) | +-------------------+-----------------+-----------------+ bugwarrior-1.5.1/bugwarrior/docs/services/versionone.rst0000644000175000017500000001142412470513341025476 0ustar threebeanthreebean00000000000000VersionOne ========== You can import tasks from VersionOne using the ``versionone`` service name. Additional Requirements ----------------------- Install the following package using ``pip``: * ``v1pysdk-unofficial`` Example Service --------------- Here's an example of a VersionOne project:: [my_issue_tracker] service = versionone versionone.base_uri = https://www3.v1host.com/MyVersionOneInstance/ versionone.usermame = somebody versionone.password = hunter5 The above example is the minimum required to import issues from VersionOne. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options` or described in `Service Features`_ below. .. note:: This plugin does not infer a project name from any attribute of the version one Task or Story; it is recommended that you set the project name to use for imported tasks by either using the below `Set a Global Project Name`_ feature, or, if you require more flexibility, setting the ``project_template`` configuration option (see :ref:`field_templates`). Service Features ---------------- Restrict Task Imports to a Specific Timebox (Sprint) ++++++++++++++++++++++++++++++++++++++++++++++++++++ You can restrict imported tasks to a specific Timebox (VersionOne's internal generic name for a Sprint) -- in this example named 'Sprint 2014-09-22' -- by using the ``versionone.timebox_name`` option; for example:: versionone.timebox_name = Sprint 2014-09-22 Set a Global Project Name +++++++++++++++++++++++++ By default, this importer does not set a project name on imported tasks. Although you can gain more flexibility by using :ref:`field_templates` to generate a project name, if all you need is to set a predictable project name, you can use the ``versionone.project_name`` option; in this example, to add imported tasks to the project 'important_project':: versionone.project_name = important_project Set the Timezone Used for Due Dates +++++++++++++++++++++++++++++++++++ You can configure the timezone used for setting your tasks' due dates by setting the ``versionone.timezone`` option. By default, your local timezone will be used. For example:: versionone.timezone = America/Los_Angeles Provided UDA Fields ------------------- +-----------------------------------+-----------------------+---------------+ | Field Name | Description | Type | +===================================+=======================+===============+ | ``versiononetaskname`` | Task Name | Text (string) | +-----------------------------------+-----------------------+---------------+ | ``versiononetaskoid`` | Task Object ID | Text (string) | +-----------------------------------+-----------------------+---------------+ | ``versiononestoryoid`` | Story Object ID | Text (string) | +-----------------------------------+-----------------------+---------------+ | ``versiononestoryname`` | Story Name | Text (string) | +-----------------------------------+-----------------------+---------------+ | ``versiononetaskreference`` | Task Reference | Text (string) | +-----------------------------------+-----------------------+---------------+ | ``versiononetaskdetailestimate`` | Task Detail Estimate | Text (string) | +-----------------------------------+-----------------------+---------------+ | ``versiononetaskestimate`` | Task Estimate | Text (string) | +-----------------------------------+-----------------------+---------------+ | ``versiononetaskdescrption`` | Task Description | Text (string) | +-----------------------------------+-----------------------+---------------+ | ``versiononetasktodo`` | Task To Do | Text (string) | +-----------------------------------+-----------------------+---------------+ | ``versiononestorydetailestimate`` | Story Detail Estimate | Text (string) | +-----------------------------------+-----------------------+---------------+ | ``versiononestoryurl`` | Story URL | Text (string) | +-----------------------------------+-----------------------+---------------+ | ``versiononetaskurl`` | Task URL | Text (string) | +-----------------------------------+-----------------------+---------------+ | ``versiononestoryestimate`` | Story Estimate | Text (string) | +-----------------------------------+-----------------------+---------------+ | ``versiononestorynumber`` | Story Number | Text (string) | +-----------------------------------+-----------------------+---------------+ | ``versiononestorydescription`` | Story Description | Text (string) | +-----------------------------------+-----------------------+---------------+ bugwarrior-1.5.1/bugwarrior/docs/services/megaplan.rst0000644000175000017500000000236113111574702025074 0ustar threebeanthreebean00000000000000Megaplan ======== You can import tasks from your Megaplan instance using the ``megaplan`` service name. Additional Requirements ----------------------- Install the following package using ``pip``: * ``megaplan`` Example Service --------------- Here's an example of a Megaplan target:: [my_issue_tracker] service = megaplan megaplan.hostname = example.megaplan.ru megaplan.login = alice megaplan.password = secret megaplan.project_name = example The above example is the minimum required to import issues from Megaplab. You can also feel free to use any of the configuration options described in :ref:`common_configuration_options`. Provided UDA Fields ------------------- +-------------------+-------------------+-------------------+ | Field Name | Description | Type | +===================+===================+===================+ | ``megaplanid`` | Issue ID | Text (string) | +-------------------+-------------------+-------------------+ | ``megaplantitle`` | Title | Text (string) | +-------------------+-------------------+-------------------+ | ``megaplanurl`` | URL | Text (string) | +-------------------+-------------------+-------------------+ bugwarrior-1.5.1/bugwarrior/docs/configuration.rst0000644000175000017500000001513713111574702024341 0ustar threebeanthreebean00000000000000.. _example_configuration: Example Configuration ====================== .. example :: # Example bugwarriorrc # General stuff. [general] # Here you define a comma separated list of targets. Each of them must have a # section below determining their properties, how to query them, etc. The name # is just a symbol, and doesn't have any functional importance. targets = my_github, my_bitbucket, paj_bitbucket, moksha_trac, bz.redhat # If unspecified, the default taskwarrior config will be used. #taskrc = /path/to/.taskrc # Setting this to true will shorten links with http://da.gd/ #shorten = False # Setting this to True will include a link to the ticket in the description inline_links = False # Setting this to True will include a link to the ticket as an annotation annotation_links = True # Setting this to True will include issue comments and author name in task # annotations annotation_comments = True # Defines whether or not issues should be matched based upon their description. # In legacy mode, we will attempt to match issues to bugs based upon the # presence of the '(bw)' marker in the task description. # If this is false, we will only select issues having the appropriate UDA # fields defined (which is smarter, better, newer, etc..) legacy_matching = False # log.level specifies the verbosity. The default is DEBUG. # log.level can be one of DEBUG, INFO, WARNING, ERROR, CRITICAL, DISABLED #log.level = DEBUG # If log.file is specified, output will be redirected there. If it remains # unspecified, output is sent to sys.stderr #log.file = /var/log/bugwarrior.log # Configure the default description or annotation length. #annotation_length = 45 # Use hooks to run commands prior to importing from bugwarrior-pull. # bugwarrior-pull will run the commands in the order that they are specified # below. # # pre_import: The pre_import hook is invoked after all issues have been pulled # from remote sources, but before they are synced to the TW db. If your # pre_import script has a non-zero exit code, the `bugwarrior-pull` command will # exit early. [hooks] pre_import = /home/someuser/backup.sh, /home/someuser/sometask.sh # This section is for configuring notifications when bugwarrior-pull runs, # and when issues are created, updated, or deleted by bugwarrior-pull. # Three backends are currently supported: # # - growlnotify (v2) Mac OS X "gntp" must be installed # - gobject Linux python gobject must be installed # # To configure, adjust the settings below. Note that neither of the # # "sticky" options have any effect on Linux. They only work for # growlnotify. #[notifications] # notifications = True # backend = growlnotify # finished_querying_sticky = False # task_crud_sticky = True # only_on_new_tasks = True # This is a github example. It says, "scrape every issue from every repository # on http://github.com/ralphbean. It doesn't matter if ralphbean owns the issue # or not." [my_github] service = github default_priority = H add_tags = open_source # This specifies that we should pull issues from repositories belonging # to the 'ralphbean' github account. See the note below about # 'github.username' and 'github.login'. They are different, and you need # both. github.username = ralphbean # I want taskwarrior to include issues from all my repos, except these # two because they're spammy or something. github.exclude_repos = project_bar,project_baz # Working with a large number of projects, instead of excluding most of them I # can also simply include just a limited set. github.include_repos = project_foo,project_foz # Note that login and username can be different: I can login as me, but # scrape issues from an organization's repos. # # - 'github.login' is the username you ask bugwarrior to # login as. Set it to your account. # - 'github.username' is the github entity you want to pull # issues for. It could be you, or some other user entirely. github.login = ralphbean github.password = OMG_LULZ # Here's an example of a trac target. [moksha_trac] service = trac trac.base_uri = fedorahosted.org/moksha trac.username = ralph trac.password = OMG_LULZ only_if_assigned = ralph also_unassigned = True default_priority = H add_tags = work # Here's an example of a megaplan target. [my_megaplan] service = megaplan megaplan.hostname = example.megaplan.ru megaplan.login = alice megaplan.password = secret megaplan.project_name = example # Here's an example of a jira project. The ``jira-python`` module is # a bit particular, and jira deployments, like Bugzilla, tend to be # reasonably customized. So YMMV. The ``base_uri`` must not have a # have a trailing slash. In this case we fetch comments and # cases from jira assigned to 'ralph' where the status is not closed or # resolved. [jira_project] service = jira jira.base_uri = https://jira.example.org jira.username = ralph jira.password = OMG_LULZ jira.query = assignee = ralph and status != closed and status != resolved # Set this to your jira major version. We currently support only jira version # 4 and 5(the default). You can find your particular version in the footer at # the dashboard. jira.version = 5 add_tags = enterprisey work # Here's an example of a phabricator target [my_phabricator] service = phabricator # No need to specify credentials. They are gathered from ~/.arcrc # Here's an example of a teamlab target. [my_teamlab] service = teamlab teamlab.hostname = teamlab.example.com teamlab.login = alice teamlab.password = secret teamlab.project_name = example_teamlab # Here's an example of a redmine target. [my_redmine] service = redmine redmine.url = http://redmine.example.org/ redmine.key = c0c4c014cafebabe redmine.user_id = 7 redmine.project_name = redmine add_tags = chiliproject [activecollab] service = activecollab activecollab.url = https://ac.example.org/api.php activecollab.key = your-api-key activecollab.user_id = 15 add_tags = php [activecollab2] service = activecollab2 activecollab2.url = http://ac.example.org/api.php activecollab2.key = your-api-key activecollab2.user_id = 15 activecollab2.projects = 1:first_project, 5:another_project bugwarrior-1.5.1/bugwarrior/docs/common_configuration.rst0000644000175000017500000002063113111574702025704 0ustar threebeanthreebean00000000000000How to Configure ================ First, add a file named ``.config/bugwarrior/bugwarriorrc`` to your home folder. This file must include at least a ``[general]`` section including the following option: * ``targets``: A comma-separated list of *other* section names to use as task sources. Optional options include: * ``taskrc``: Specify which TaskRC configuration file to use. By default, will use the system default (usually ``~/.taskrc``). * ``shorten``: Set to ``True`` to shorten links. * ``inline_links``: When ``False``, links are appended as an annotation. Defaults to ``True``. * ``annotation_links``: When ``True`` will include a link to the ticket as an annotation. Defaults to ``False``. * ``annotation_comments``: When ``False`` skips putting issue comments into annotations. Defaults to ``True``. * ``legacy_matching``: Set to ``False`` to instruct Bugwarrior to match issues using only the issue's unique identifiers (rather than matching on description). * ``log.level``: Set to one of ``DEBUG``, ``INFO``, ``WARNING``, ``ERROR``, ``CRITICAL``, or ``DISABLED`` to control the logging verbosity. By default, this is set to ``DEBUG``. * ``log.file``: Set to the path at which you would like logging messages written. By default, logging messages will be written to stderr. * ``annotation_length``: Import maximally this number of characters of incoming annotations. Default: 45. * ``description_length``: Use maximally this number of characters in the description. Default: 35. * ``merge_annotations``: If ``False``, bugwarrior won't bother with adding annotations to your tasks at all. Default: ``True``. * ``merge_tags``: If ``False``, bugwarrior won't bother with adding tags to your tasks at all. Default: ``True``. * ``static_fields``: A comma separated list of attributes that shouldn't be *updated* by bugwarrior. Use for values that you want to tune manually. Default: ``priority``. In addition to the ``[general]`` section, sections may be named ``[flavor.myflavor]`` and may be selected using the ``--flavor`` option to ``bugwarrior-pull``. This section will then be used rather than the ``[general]`` section. A more-detailed example configuration can be found at :ref:`example_configuration`. .. _common_configuration_options: Common Service Configuration Options ------------------------------------ All services support the following configuration options in addition to any specified service features or settings specified in the service example: * ``only_if_assigned``: Only import issues assigned to the specified user. * ``also_unassigned``: Same as above, but also create tasks for issues that are not assigned to anybody. * ``default_priority``: Assign this priority ('L', 'M', or 'H') to newly-imported issues. * ``add_tags``: A comma-separated list of tags to add to an issue. In most cases, this will just be a series of strings, but you can also make tags by defining one of your tags following the example set in `Field Templates`_. .. _field_templates: Field Templates --------------- By default, Bugwarrior will import issues with a fairly verbose description template looking something like this:: (BW)Issue#10 - Fix perpetual motion machine .. http://media.giphy.com/media/LldEzRPqyo2Yg/giphy.gif but depending upon your workflow, the information presented may not be useful to you. To help users build descriptions that suit their needs, all services allow one to specify a ``SERVICE.description_template`` configuration option, in which one can enter a one-line Jinja template. The context available includes all Taskwarrior fields and all UDAs (see section named 'Provided UDA Fields' for each service) defined for the relevant service. .. note:: Jinja templates can be very complex. For more details about Jinja templates, please consult `Jinja's Template Documentation `_. For example, to pull-in Github issues assigned to `@ralphbean `_, setting the issue description such that it is composed of only the Github issue number and title, you could create a service entry like this:: [ralphs_github_account] service = github github.username = ralphbean github.description_template = {{githubnumber}}: {{githubtitle}} You can also use this tool for altering the generated value of any other Taskwarrior record field by using the same kind of template. Uppercasing the project name for imported issues:: SERVICE.project_template = {{project|upper}} You can also use this feature to override the generated value of any field. This example causes imported issues to be assigned to the 'Office' project regardless of what project was assigned by the service itself:: SERVICE.project_template = Office Password Management ------------------- You need not store your password in plain text in your `bugwarriorrc` file; you can enter the following values to control where to gather your password from: ``password = @oracle:use_keyring`` Retrieve a password from the system keyring. The ``bugwarrior-vault`` command line tool can be used to manage your passwords as stored in your keyring (say to reset them or clear them). Extra dependencies must be installed with `pip install bugwarrior[keyring]` to enable this feature. ``password = @oracle:ask_password`` Ask for a password at runtime. ``password = @oracle:eval:`` Use the output of as the password. For instance, to integrate bugwarrior with the password manager `pass `_ you can use ``@oracle:eval:pass my/password``. Hooks ----- Use hooks to run commands prior to importing from bugwarrior-pull. bugwarrior-pull will run the commands in the order that they are specified below. To use hooks, add a ``[hooks]`` section to your configuration, mapping the hook you'd like to use with a comma-separated list of scripts to execute. :: [hooks] pre_import = /home/someuser/backup.sh, /home/someuser/sometask.sh Hook options: * ``pre_import``: The pre_import hook is invoked after all issues have been pulled from remote sources, but before they are synced to the TW db. If your pre_import script has a non-zero exit code, the ``bugwarrior-pull`` command will exit early. Notifications ------------- Add a ``[notifications]`` section to your configuration to receive notifications when a bugwarrior pull runs, and when issues are created, updated, or deleted by ``bugwarrior-pull``:: [notifications] notifications = True backend = growlnotify finished_querying_sticky = False task_crud_sticky = True only_on_new_tasks = True Backend options: +------------------+------------------+-------------------------+ | Backend Name | Operating System | Required Python Modules | +==================+==================+=========================+ | ``growlnotify`` | MacOS X | ``gntp`` | +------------------+------------------+-------------------------+ | ``gobject`` | Linux | ``gobject`` | +------------------+------------------+-------------------------+ .. note:: The ``finished_querying_sticky`` and ``task_crud_sticky`` options have no effect if you are using a notification backend other than ``growlnotify``. Configuration files ------------------- bugwarrior will look at the following paths and read its configuration from the first existing file in this order: * :file:`~/.config/bugwarrior/bugwarriorrc` * :file:`~/.bugwarriorrc` * :file:`/etc/xdg/bugwarrior/bugwarriorrc` The default paths can be altered using the environment variables :envvar:`BUGWARRIORRC`, :envvar:`XDG_CONFIG_HOME` and :envvar:`XDG_CONFIG_DIRS`. Environment Variables --------------------- .. envvar:: BUGWARRIORRC This overrides the default RC file. .. envvar:: XDG_CONFIG_HOME By default, :program:`bugwarrior` looks for a configuration file named ``$XDG_CONFIG_HOME/bugwarrior/bugwarriorrc``. If ``$XDG_CONFIG_HOME`` is either not set or empty, a default equal to ``$HOME/.config`` is used. .. envvar:: XDG_CONFIG_DIRS If it can't find a user-specific configuration file (either ``$XDG_CONFIG_HOME/bugwarrior/bugwarriorrc`` or ``$HOME/.bugwarriorrc``), :program:`bugwarrior` looks through the directories in ``$XDG_CONFIG_DIRS`` for a configuration file named ``bugwarrior/bugwarriorrc``. The directories in ``$XDG_CONFIG_DIRS`` should be separated with a colon ':'. If ``$XDG_CONFIG_DIRS`` is either not set or empty, a value equal to ``/etc/xdg`` is used. bugwarrior-1.5.1/bugwarrior/docs/using.rst0000644000175000017500000000442513111574702022615 0ustar threebeanthreebean00000000000000How to use ========== Just run ``bugwarrior-pull``. Cron ---- It's ideal to create a cron task like:: */15 * * * * /usr/bin/bugwarrior-pull Bugwarrior can emit desktop notifications when it adds or completes issues to and from your local ``~/.task/`` db. If your ``bugwarriorrc`` file has notifications turned on, you'll also need to tell cron which display to use by adding the following to your crontab:: DISPLAY=:0 */15 * * * * /usr/bin/bugwarrior-pull systemd timer ------------- If you would prefer to use a systemd timer to run ``bugwarrior-pull`` on a schedule, you can create the following two files:: $ cat ~/.config/systemd/user/bugwarrior-pull.service [Unit] Description=bugwarrior-pull [Service] Environment="DISPLAY=:0" ExecStart=/usr/bin/bugwarrior-pull Type=oneshot [Install] WantedBy=default.target $ cat ~/.config/systemd/user/bugwarrior-pull.timer [Unit] Description=Run bugwarrior-pull hourly and on boot [Timer] OnBootSec=15min OnUnitActiveSec=1h [Install] WantedBy=timers.target Once those files are in place, you can start and enable the timer:: $ systemctl --user enable bugwarrior-pull.timer $ systemctl --user start bugwarrior-pull.timer Exporting a list of UDAs ------------------------ Most services define a set of UDAs in which bugwarrior store extra information about the incoming ticket. Usually, this includes things like the title of the ticket and its URL, but some services provide an extensive amount of metadata. See each service's documentation for more information. For using this data in reports, it is recommended that you add these UDA definitions to your ``taskrc`` file. You can generate your list of UDA definitions by running the following command:: bugwarrior-uda You can add those lines verbatim to your ``taskrc`` file if you would like Taskwarrior to know the human-readable name and data type for the defined UDAs. .. note:: Not adding those lines to your ``taskrc`` file will have no negative effects aside from Taskwarrior not knowing the human-readable name for the field, but depending on what version of Taskwarrior you are using, it may prevent you from changing the values of those fields or using them in filter expressions. bugwarrior-1.5.1/bugwarrior/docs/faq.rst0000644000175000017500000000103313111574702022227 0ustar threebeanthreebean00000000000000FAQ === Can bugwarrior support ? ---------------------------------------------------- Sure! But our general rule here is that we won't write a backend for a service unless we use it personally, otherwise it's hard to be sure that it really works. We also try to rely on people to become maintainers of the different backend plugins they use so that they don't suffer bit rot over time. In summary, we need someone who 1) uses and 2) can develop the plugin. Could it be you? :) bugwarrior-1.5.1/bugwarrior/docs/getting.rst0000644000175000017500000000443113111574702023126 0ustar threebeanthreebean00000000000000Getting bugwarrior ================== .. _requirements: Requirements ------------ To use bugwarrior, you need python 2.7 and taskwarrior. Upon installation, the setup script will automatically download and install missing python dependencies. Note that some of those dependencies have a C extension module (e.g. the ``cryptography`` package). If those packages are not yet present on your system, the setup script will try to build them locally, for which you will need a C compiler (e.g. ``gcc``) and the necessary header files (python and, for the cryptography package, openssl). A convenient way to install those is to use your usual package manager (``dnf``, ``yum``, ``apt``, etc). Header files are installed from development packages (e.g. ``python-devel`` and ``openssl-devel`` on Fedora or ``python-dev`` ``libssl-dev`` on Debian). Installing from the Python Package Index ---------------------------------------- .. highlight:: console Installing it from http://pypi.python.org/pypi/bugwarrior is easy with :command:`pip`:: $ pip install bugwarrior Alternatively, you can use :command:`easy_install` if you prefer:: $ easy_install bugwarrior By default, ``bugwarrior`` will be installed with support for the following services: Bitbucket, Bugzilla, Github, Gitlab, Pagure, Phabricator, Redmine, Teamlab, Track and Versionone. There is optional support for Jira, Megaplan.ru and Active Collab but those require extra dependencies that are installed by specifying ``bugwarrior[service]`` in the commands above. For example, if you want to use bugwarrior with Jira:: $ pip install "bugwarrior[jira]" Installing from Source ---------------------- You can find the source on github at http://github.com/ralphbean/bugwarrior. Either fork/clone if you plan to do development on bugwarrior, or you can simply download the latest tarball:: $ wget https://github.com/ralphbean/bugwarrior/tarball/master -O bugwarrior-latest.tar.gz $ tar -xzvf bugwarrior-latest.tar.gz $ cd ralphbean-bugwarrior-* $ python setup.py install Installing from Distribution Packages ------------------------------------- bugwarrior has been packaged for Fedora. You can install it with the standard :command:`dnf` (:command:`yum`) package management tools as follows:: $ sudo dnf install bugwarrior bugwarrior-1.5.1/bugwarrior/docs/index.rst0000644000175000017500000000226513111574702022577 0ustar threebeanthreebean00000000000000.. Bugwarrior documentation master file, created by sphinx-quickstart on Wed Apr 16 15:09:22 2014. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Bugwarrior ========== ``bugwarrior`` is a command line utility for updating your local `taskwarrior `_ database from your forge issue trackers. Build Status ------------ .. |master| image:: https://secure.travis-ci.org/ralphbean/bugwarrior.png?branch=master :alt: Build Status - master branch :target: http://travis-ci.org/#!/ralphbean/bugwarrior .. |develop| image:: https://secure.travis-ci.org/ralphbean/bugwarrior.png?branch=develop :alt: Build Status - develop branch :target: http://travis-ci.org/#!/ralphbean/bugwarrior +----------+-----------+ | Branch | Status | +==========+===========+ | master | |master| | +----------+-----------+ | develop | |develop| | +----------+-----------+ Contents -------- .. toctree:: :maxdepth: 2 getting using common_configuration services configuration contributing faq Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` bugwarrior-1.5.1/bugwarrior/docs/contributing.rst0000644000175000017500000000402513111574702024173 0ustar threebeanthreebean00000000000000How to Contribute ================= .. highlight:: console Setting up your development environment --------------------------------------- First, make sure you have the necessary :ref:`requirements`. You should also install the `virtualenv `_ tool for python. (I use a wrapper for it called `virtualenvwrapper `_ which is awesome but not required.) Virtualenv will help isolate your dependencies from the rest of your system. :: $ sudo yum install python-virtualenv git $ mkdir -p ~/virtualenvs/ $ virtualenv ~/virtualenvs/bugwarrior You should now have a virtualenv in a ``~/virtualenvs/`` directory. To use it, you need to "activate" it like this:: $ source ~/virtualenv/bugwarrior/bin/activate (bugwarrior)$ which python At any time, you can deactivate it by typing ``deactivate`` at the command prompt. Next step -- get the code! :: (bugwarrior)$ git clone git@github.com:ralphbean/bugwarrior.git (bugwarrior)$ cd bugwarrior (bugwarrior)$ python setup.py develop (bugwarrior)$ which bugwarrior-pull This will actually run it.. be careful and back up your task directory! :: (bugwarrior)$ bugwarrior-pull Making a pull request --------------------- Create a new branch for each pull request based off the ``develop`` branch:: (bugwarrior)$ git checkout -b my-shiny-new-feature develop Please add tests when appropriate and run the test suite before opening a PR:: (bugwarrior)$ python setup.py nosetests We look forward to your contribution! Works in progress ----------------- The best way to get help and feedback before you pour too much time and effort into your branch is to open a "work in progress" pull request. We will not leave it open indefinitely if it doesn't seem to be progressing, but there's nothing to lose in soliciting some pointers and concerns. Please begin the title of your work in progress pr with "[WIP]" and explain what remains to be done or what you're having trouble with. bugwarrior-1.5.1/bugwarrior/README.rst0000644000175000017500000000473313111574702021477 0ustar threebeanthreebean00000000000000bugwarrior - Pull tickets from github, bitbucket, bugzilla, jira, trac, and others into taskwarrior =================================================================================================== .. split here ``bugwarrior`` is a command line utility for updating your local `taskwarrior `_ database from your forge issue trackers. It currently supports the following remote resources: - `github `_ (api v3) - `gitlab `_ (api v3) - `bitbucket `_ - `pagure `_ - `bugzilla `_ - `trac `_ - `megaplan `_ - `teamlab `_ - `redmine `_ - `jira `_ - `taiga `_ - `gerrit `_ - `activecollab `_ (2.x and 4.x) - `phabricator `_ - `versionone `_ - `trello `_ - `youtrack `_ Documentation ------------- For information on how to install and use bugwarrior, read `the docs `_ on RTFD. Build Status ------------ .. |master| image:: https://secure.travis-ci.org/ralphbean/bugwarrior.png?branch=master :alt: Build Status - master branch :target: https://travis-ci.org/#!/ralphbean/bugwarrior .. |develop| image:: https://secure.travis-ci.org/ralphbean/bugwarrior.png?branch=develop :alt: Build Status - develop branch :target: https://travis-ci.org/#!/ralphbean/bugwarrior +----------+-----------+ | Branch | Status | +==========+===========+ | master | |master| | +----------+-----------+ | develop | |develop| | +----------+-----------+ Contributors ------------ - Ralph Bean (primary author) - Ben Boeckel (contributed support for Gitlab) - Justin Forest (contributed support for RedMine, TeamLab, and MegaPlan, as well as some unicode help) - Tycho Garen (contributed support for Jira) - Kosta Harlan (contributed support for activeCollab, notifications, and experimental taskw support) - Luke Macken (contributed some code cleaning) - James Rowe (contributed to the docs) - Adam Coddington (anti-entropy crusader) - Iain R. Learmonth (contributed support for the Debian BTS and maintains the Debian package) - BinaryBabel (contributed support for YouTrack) bugwarrior-1.5.1/bugwarrior/command.py0000644000175000017500000001153213111574702021773 0ustar threebeanthreebean00000000000000from __future__ import print_function import os import sys from lockfile import LockTimeout from lockfile.pidlockfile import PIDLockFile import getpass import click from bugwarrior.config import ( get_data_path, get_keyring, load_config, ServiceConfig) from bugwarrior.services import aggregate_issues, get_service from bugwarrior.db import ( get_defined_udas_as_strings, synchronize, ) import logging log = logging.getLogger(__name__) # We overwrite 'list' further down. lst = list def _get_section_name(flavor): if flavor: return 'flavor.' + flavor return 'general' def _try_load_config(main_section, interactive=False): try: return load_config(main_section, interactive) except IOError: # Our standard logging configuration depends on the bugwarrior # configuration file which just failed to load. logging.basicConfig() exc_info = sys.exc_info() log.critical("Could not load configuration. " "Maybe you have not created a configuration file.", exc_info=(exc_info[0], exc_info[1], None)) sys.exit(1) @click.command() @click.option('--dry-run', is_flag=True) @click.option('--flavor', default=None, help='The flavor to use') @click.option('--interactive', is_flag=True) @click.option('--debug', is_flag=True, help='Do not use multiprocessing (which breaks pdb).') def pull(dry_run, flavor, interactive, debug): """ Pull down tasks from forges and add them to your taskwarrior tasks. Relies on configuration in bugwarriorrc """ try: main_section = _get_section_name(flavor) config = _try_load_config(main_section, interactive) lockfile_path = os.path.join(get_data_path(config, main_section), 'bugwarrior.lockfile') lockfile = PIDLockFile(lockfile_path) lockfile.acquire(timeout=10) try: # Get all the issues. This can take a while. issue_generator = aggregate_issues(config, main_section, debug) # Stuff them in the taskwarrior db as necessary synchronize(issue_generator, config, main_section, dry_run) finally: lockfile.release() except LockTimeout: log.critical( 'Your taskrc repository is currently locked. ' 'Remove the file at %s if you are sure no other ' 'bugwarrior processes are currently running.' % ( lockfile_path ) ) except RuntimeError as e: log.critical("Aborted (%s)" % e) @click.group() def vault(): """ Password/keyring management for bugwarrior. If you use the keyring password oracle in your bugwarrior config, this tool can be used to manage your keyring. """ pass def targets(): config = load_config('general') for section in config.sections(): if section in ['general', 'notifications'] or \ section.startswith('flavor.'): continue service_name = config.get(section, 'service') service_class = get_service(service_name) for option in config.options(section): value = config.get(section, option) if not value: continue if '@oracle:use_keyring' in value: service_config = ServiceConfig( service_class.CONFIG_PREFIX, config, section) yield service_class.get_keyring_service(service_config) @vault.command() def list(): pws = lst(targets()) print("%i @oracle:use_keyring passwords in bugwarriorrc" % len(pws)) for section in pws: print("-", section) @vault.command() @click.argument('target') @click.argument('username') def clear(target, username): target_list = lst(targets()) if target not in target_list: raise ValueError("%s must be one of %r" % (target, target_list)) keyring = get_keyring() if keyring.get_password(target, username): keyring.delete_password(target, username) print("Password cleared for %s, %s" % (target, username)) else: print("No password found for %s, %s" % (target, username)) @vault.command() @click.argument('target') @click.argument('username') def set(target, username): target_list = lst(targets()) if target not in target_list: raise ValueError("%s must be one of %r" % (target, target_list)) keyring = get_keyring() keyring.set_password(target, username, getpass.getpass()) print("Password set for %s, %s" % (target, username)) @click.command() @click.option('--flavor', default=None, help='The flavor to use') def uda(flavor): main_section = _get_section_name(flavor) conf = _try_load_config(main_section) print("# Bugwarrior UDAs") for uda in get_defined_udas_as_strings(conf, main_section): print(uda) print("# END Bugwarrior UDAs") bugwarrior-1.5.1/bugwarrior/__init__.py0000644000175000017500000000014512470513341022111 0ustar threebeanthreebean00000000000000# from bugwarrior.command import pull, vault, uda __all__ = [ 'pull', 'vault', 'uda', ] bugwarrior-1.5.1/LICENSE.txt0000644000175000017500000010451312421224277017447 0ustar threebeanthreebean00000000000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . bugwarrior-1.5.1/README.rst0000644000175000017500000000473313111574702017314 0ustar threebeanthreebean00000000000000bugwarrior - Pull tickets from github, bitbucket, bugzilla, jira, trac, and others into taskwarrior =================================================================================================== .. split here ``bugwarrior`` is a command line utility for updating your local `taskwarrior `_ database from your forge issue trackers. It currently supports the following remote resources: - `github `_ (api v3) - `gitlab `_ (api v3) - `bitbucket `_ - `pagure `_ - `bugzilla `_ - `trac `_ - `megaplan `_ - `teamlab `_ - `redmine `_ - `jira `_ - `taiga `_ - `gerrit `_ - `activecollab `_ (2.x and 4.x) - `phabricator `_ - `versionone `_ - `trello `_ - `youtrack `_ Documentation ------------- For information on how to install and use bugwarrior, read `the docs `_ on RTFD. Build Status ------------ .. |master| image:: https://secure.travis-ci.org/ralphbean/bugwarrior.png?branch=master :alt: Build Status - master branch :target: https://travis-ci.org/#!/ralphbean/bugwarrior .. |develop| image:: https://secure.travis-ci.org/ralphbean/bugwarrior.png?branch=develop :alt: Build Status - develop branch :target: https://travis-ci.org/#!/ralphbean/bugwarrior +----------+-----------+ | Branch | Status | +==========+===========+ | master | |master| | +----------+-----------+ | develop | |develop| | +----------+-----------+ Contributors ------------ - Ralph Bean (primary author) - Ben Boeckel (contributed support for Gitlab) - Justin Forest (contributed support for RedMine, TeamLab, and MegaPlan, as well as some unicode help) - Tycho Garen (contributed support for Jira) - Kosta Harlan (contributed support for activeCollab, notifications, and experimental taskw support) - Luke Macken (contributed some code cleaning) - James Rowe (contributed to the docs) - Adam Coddington (anti-entropy crusader) - Iain R. Learmonth (contributed support for the Debian BTS and maintains the Debian package) - BinaryBabel (contributed support for YouTrack) bugwarrior-1.5.1/tests/0000755000175000017500000000000013111575230016755 5ustar threebeanthreebean00000000000000bugwarrior-1.5.1/tests/test_gitlab.py0000644000175000017500000001642013111574702021636 0ustar threebeanthreebean00000000000000from future import standard_library standard_library.install_aliases() from builtins import next import configparser import datetime import pytz import responses from bugwarrior.config import ServiceConfig from bugwarrior.services.gitlab import GitlabService from .base import ConfigTest, ServiceTest, AbstractServiceTest class TestGitlabService(ConfigTest): def setUp(self): super(TestGitlabService, self).setUp() self.config = configparser.RawConfigParser() self.config.add_section('general') self.config.add_section('myservice') self.config.set('myservice', 'gitlab.login', 'foobar') self.config.set('myservice', 'gitlab.token', 'XXXXXX') self.service_config = ServiceConfig( GitlabService.CONFIG_PREFIX, self.config, 'myservice') def test_get_keyring_service_default_host(self): self.assertEqual( GitlabService.get_keyring_service(self.service_config), 'gitlab://foobar@gitlab.com') def test_get_keyring_service_custom_host(self): self.config.set('myservice', 'gitlab.host', 'gitlab.example.com') self.assertEqual( GitlabService.get_keyring_service(self.service_config), 'gitlab://foobar@gitlab.example.com') def test_add_default_namespace_to_included_repos(self): self.config.set('myservice', 'gitlab.include_repos', 'baz, banana/tree') service = GitlabService(self.config, 'general', 'myservice') self.assertEqual(service.include_repos, ['foobar/baz', 'banana/tree']) def test_add_default_namespace_to_excluded_repos(self): self.config.set('myservice', 'gitlab.exclude_repos', 'baz, banana/tree') service = GitlabService(self.config, 'general', 'myservice') self.assertEqual(service.exclude_repos, ['foobar/baz', 'banana/tree']) class TestGitlabIssue(AbstractServiceTest, ServiceTest): maxDiff = None SERVICE_CONFIG = { 'gitlab.host': 'gitlab.example.com', 'gitlab.login': 'arbitrary_login', 'gitlab.token': 'arbitrary_token', } arbitrary_created = ( datetime.datetime.utcnow() - datetime.timedelta(hours=1) ).replace(tzinfo=pytz.UTC, microsecond=0) arbitrary_updated = datetime.datetime.utcnow().replace( tzinfo=pytz.UTC, microsecond=0) arbitrary_duedate = ( datetime.datetime.combine(datetime.date.today(), datetime.datetime.min.time()) ).replace(tzinfo=pytz.UTC) arbitrary_issue = { "id": 42, "iid": 3, "project_id": 8, "title": "Add user settings", "description": "", "labels": [ "feature" ], "milestone": { "id": 1, "title": "v1.0", "description": "", "due_date": arbitrary_duedate.date().isoformat(), "state": "closed", "updated_at": "2012-07-04T13:42:48Z", "created_at": "2012-07-04T13:42:48Z" }, "assignee": { "id": 2, "username": "jack_smith", "email": "jack@example.com", "name": "Jack Smith", "state": "active", "created_at": "2012-05-23T08:01:01Z" }, "author": { "id": 1, "username": "john_smith", "email": "john@example.com", "name": "John Smith", "state": "active", "created_at": "2012-05-23T08:00:58Z" }, "state": "opened", "updated_at": arbitrary_updated.isoformat(), "created_at": arbitrary_created.isoformat(), } arbitrary_extra = { 'issue_url': 'https://gitlab.example.com/arbitrary_username/project/issues/3', 'project': 'project', 'type': 'issue', 'annotations': [], } def setUp(self): super(TestGitlabIssue, self).setUp() self.service = self.get_mock_service(GitlabService) def test_normalize_label_to_tag(self): issue = self.service.get_issue_for_record( self.arbitrary_issue, self.arbitrary_extra ) self.assertEqual(issue._normalize_label_to_tag('needs work'), 'needs_work') def test_to_taskwarrior(self): self.service.import_labels_as_tags = True issue = self.service.get_issue_for_record( self.arbitrary_issue, self.arbitrary_extra ) expected_output = { 'project': self.arbitrary_extra['project'], 'priority': self.service.default_priority, 'annotations': [], 'tags': [u'feature'], issue.URL: self.arbitrary_extra['issue_url'], issue.REPO: 'project', issue.STATE: self.arbitrary_issue['state'], issue.TYPE: self.arbitrary_extra['type'], issue.TITLE: self.arbitrary_issue['title'], issue.NUMBER: self.arbitrary_issue['iid'], issue.UPDATED_AT: self.arbitrary_updated.replace(microsecond=0), issue.CREATED_AT: self.arbitrary_created.replace(microsecond=0), issue.DUEDATE: self.arbitrary_duedate, issue.DESCRIPTION: self.arbitrary_issue['description'], issue.MILESTONE: self.arbitrary_issue['milestone']['title'], issue.UPVOTES: 0, issue.DOWNVOTES: 0, issue.WORK_IN_PROGRESS: 0, issue.AUTHOR: 'john_smith', issue.ASSIGNEE: 'jack_smith', } actual_output = issue.to_taskwarrior() self.assertEqual(actual_output, expected_output) @responses.activate def test_issues(self): self.add_response( 'https://gitlab.example.com/api/v3/projects?per_page=100&page=1', json=[{ 'id': 1, 'path': 'arbitrary_username/project', 'web_url': 'example.com' }]) self.add_response( 'https://gitlab.example.com/api/v3/projects/1/issues?per_page=100&page=1', json=[self.arbitrary_issue]) self.add_response( 'https://gitlab.example.com/api/v3/projects/1/issues/42/notes?per_page=100&page=1', json=[{ 'author': {'username': 'john_smith'}, 'body': 'Some comment.' }]) issue = next(self.service.issues()) expected = { 'annotations': [u'@john_smith - Some comment.'], 'description': u'(bw)Is#3 - Add user settings .. example.com/issues/3', 'gitlabassignee': u'jack_smith', 'gitlabauthor': u'john_smith', 'gitlabcreatedon': self.arbitrary_created, 'gitlabdescription': u'', 'gitlabdownvotes': 0, 'gitlabmilestone': u'v1.0', 'gitlabnumber': 3, 'gitlabrepo': u'arbitrary_username/project', 'gitlabstate': u'opened', 'gitlabtitle': u'Add user settings', 'gitlabtype': 'issue', 'gitlabupdatedat': self.arbitrary_updated, 'gitlabduedate': self.arbitrary_duedate, 'gitlabupvotes': 0, 'gitlaburl': u'example.com/issues/3', 'gitlabwip': 0, 'priority': 'M', 'project': u'arbitrary_username/project', 'tags': []} self.assertEqual(issue.get_taskwarrior_record(), expected) bugwarrior-1.5.1/tests/test_activecollab2.py0000644000175000017500000000660613111574702023113 0ustar threebeanthreebean00000000000000from builtins import next import re import datetime import pytz import responses from bugwarrior.services.activecollab2 import ActiveCollab2Service from .base import ServiceTest, AbstractServiceTest class TestActiveCollab2Issue(AbstractServiceTest, ServiceTest): SERVICE_CONFIG = { 'activecollab2.url': 'http://hello', 'activecollab2.key': 'howdy', 'activecollab2.user_id': 0, 'activecollab2.projects': '1:one, 2:two' } arbitrary_due_on = ( datetime.datetime.now() - datetime.timedelta(hours=1) ).replace(tzinfo=pytz.UTC) arbitrary_created_on = ( datetime.datetime.now() - datetime.timedelta(hours=2) ).replace(tzinfo=pytz.UTC) arbitrary_issue = { 'project': 'something', 'priority': 2, 'due_on': arbitrary_due_on.isoformat(), 'permalink': 'http://wherever/', 'ticket_id': 10, 'project_id': 20, 'type': 'Ticket', 'created_on': arbitrary_created_on.isoformat(), 'created_by_id': '10', 'body': 'Ticket Body', 'name': 'Anonymous', 'assignees': [ {'user_id': SERVICE_CONFIG['activecollab2.user_id'], 'is_owner': True} ], 'description': 'Further detail.', } def setUp(self): super(TestActiveCollab2Issue, self).setUp() self.service = self.get_mock_service(ActiveCollab2Service) def test_to_taskwarrior(self): issue = self.service.get_issue_for_record(self.arbitrary_issue) expected_output = { 'project': self.arbitrary_issue['project'], 'priority': issue.PRIORITY_MAP[self.arbitrary_issue['priority']], 'due': self.arbitrary_due_on, issue.PERMALINK: self.arbitrary_issue['permalink'], issue.TICKET_ID: self.arbitrary_issue['ticket_id'], issue.PROJECT_ID: self.arbitrary_issue['project_id'], issue.TYPE: self.arbitrary_issue['type'], issue.CREATED_ON: self.arbitrary_created_on, issue.CREATED_BY_ID: self.arbitrary_issue['created_by_id'], issue.BODY: self.arbitrary_issue['body'], issue.NAME: self.arbitrary_issue['name'], } actual_output = issue.to_taskwarrior() self.assertEqual(actual_output, expected_output) @responses.activate def test_issues(self): self.add_response( re.compile( 'http://hello/\?(?=.*token=howdy)(?=.*path_info=\%2Fprojects\%2F[1-2]\%2Fuser-tasks)(?=.*format=json)'), json=[self.arbitrary_issue]) self.add_response( re.compile( 'http://hello/\?(?=.*token=howdy)(?=.*path_info=\%2Fprojects\%2F20\%2Ftickets\%2F10)(?=.*format=json)'), json=self.arbitrary_issue) issue = next(self.service.issues()) expected = { 'ac2body': u'Ticket Body', 'ac2createdbyid': u'10', 'ac2createdon': self.arbitrary_created_on, 'ac2name': u'Anonymous', 'ac2permalink': u'http://wherever/', 'ac2projectid': 20, 'ac2ticketid': 10, 'ac2type': u'Ticket', 'description': u'(bw)Is#10 - Anonymous .. http://wherever/', 'due': self.arbitrary_due_on, 'priority': 'H', 'project': u'something', 'tags': []} self.assertEqual(issue.get_taskwarrior_record(), expected) bugwarrior-1.5.1/tests/test_taiga.pyc0000644000175000017500000000547513111575063021635 0ustar threebeanthreebean00000000000000ó Âù&Yc@s_ddlmZddlZddlmZddlmZmZdeefd„ƒYZdS(iÿÿÿÿ(tnextN(t TaigaServicei(t ServiceTesttAbstractServiceTesttTestTaigaIssuecBsleZidd6dd6Zidd6dd6dd 6d d 6d gd 6Zd„Zd„Zejd„ƒZRS(s https://onestaiga.base_urittwostaiga.auth_tokenitiditprojecti(trefsthis is a titletsubjectt bugwarriorttagscCs)tt|ƒjƒ|jtƒ|_dS(N(tsuperRtsetUptget_mock_serviceRtservice(tself((s4/home/threebean/devel/bugwarrior/tests/test_taiga.pyR scCsidd6gd6dd6}|jj|j|ƒ}|jƒ}igd6dd6dd6dgd 6d d 6d d 6dd6}|j||ƒdS(NtawesomeRt annotationss this is a urlturltMtpriorityR R i(ttaigaidsthis is a titlet taigasummaryttaigaurl(Rtget_issue_for_recordtrecordtto_taskwarriort assertEqual(Rtextratissuetactualtexpected((s4/home/threebean/devel/bugwarrior/tests/test_taiga.pyttest_to_taskwarriors    cCsd}|jddi|d6ƒ|jdj|ƒd|jgƒ|jdj|jdƒdidd 6ƒ|jd j|jdƒdiid d 6d 6dd6gƒt|jjƒƒ}idgd6dd6dd6dd6dgd6dd6dd6dd6}|j|jƒ|ƒdS(Nishttps://one/api/v1/users/metjsonRsFhttps://one/api/v1/userstories?status__is_closed=false&assigned_to={0}shttps://one/api/v1/projects/{0}Rt somethingtslugs(https://one/api/v1/history/userstory/{0}tyoutusernametusersBlah blah blah!tcommentu@you - Blah blah blah!RuB(bw)Is#40 - this is a title .. https://one/project/something/us/40t descriptionRRu somethingu bugwarriorR i(Ruthis is a titleRu#https://one/project/something/us/40R(t add_responsetformatRRRtissuesRtget_taskwarrior_record(RtuseridRR ((s4/home/threebean/devel/bugwarrior/tests/test_taiga.pyt test_issues3s4     ( t__name__t __module__tSERVICE_CONFIGRR R!t responsestactivateR/(((s4/home/threebean/devel/bugwarrior/tests/test_taiga.pyR s    ( tbuiltinsRR3tbugwarrior.services.taigaRtbaseRRR(((s4/home/threebean/devel/bugwarrior/tests/test_taiga.pyts bugwarrior-1.5.1/tests/test_jira.py0000644000175000017500000000677113111574702021331 0ustar threebeanthreebean00000000000000from builtins import next from builtins import object from collections import namedtuple import mock from dateutil.tz import tzoffset, datetime from bugwarrior.services.jira import JiraService from .base import ServiceTest, AbstractServiceTest class FakeJiraClient(object): def __init__(self, arbitrary_record): self.arbitrary_record = arbitrary_record def search_issues(self, *args, **kwargs): Case = namedtuple('Case', ['raw', 'key']) return [Case(self.arbitrary_record, self.arbitrary_record['key'])] def comments(self, *args, **kwargs): return None class TestJiraIssue(AbstractServiceTest, ServiceTest): SERVICE_CONFIG = { 'jira.username': 'one', 'jira.base_uri': 'two', 'jira.password': 'three', } arbitrary_estimation = 3600 arbitrary_id = '10' arbitrary_project = 'DONUT' arbitrary_summary = 'lkjaldsfjaldf' arbitrary_record = { 'fields': { 'priority': 'Blocker', 'summary': arbitrary_summary, 'timeestimate': arbitrary_estimation, 'created': '2016-06-06T06:07:08.123-0700', 'fixVersions': [{'name': '1.2.3'}] }, 'key': '%s-%s' % (arbitrary_project, arbitrary_id, ), } def setUp(self): super(TestJiraIssue, self).setUp() with mock.patch('jira.client.JIRA._get_json'): self.service = self.get_mock_service(JiraService) def get_mock_service(self, *args, **kwargs): service = super(TestJiraIssue, self).get_mock_service(*args, **kwargs) service.jira = FakeJiraClient(self.arbitrary_record) return service def test_to_taskwarrior(self): arbitrary_url = 'http://one' arbitrary_extra = { 'jira_version': 5, 'annotations': ['an annotation'], } issue = self.service.get_issue_for_record( self.arbitrary_record, arbitrary_extra ) expected_output = { 'project': self.arbitrary_project, 'priority': ( issue.PRIORITY_MAP[self.arbitrary_record['fields']['priority']] ), 'annotations': arbitrary_extra['annotations'], 'tags': [], 'entry': datetime.datetime(2016, 6, 6, 6, 7, 8, 123000, tzinfo=tzoffset(None, -25200)), 'jirafixversion': '1.2.3', issue.URL: arbitrary_url, issue.FOREIGN_ID: self.arbitrary_record['key'], issue.SUMMARY: self.arbitrary_summary, issue.DESCRIPTION: None, issue.ESTIMATE: self.arbitrary_estimation / 60 / 60 } def get_url(*args): return arbitrary_url with mock.patch.object(issue, 'get_url', side_effect=get_url): actual_output = issue.to_taskwarrior() self.assertEqual(actual_output, expected_output) def test_issues(self): issue = next(self.service.issues()) expected = { 'annotations': [], 'description': '(bw)Is#10 - lkjaldsfjaldf .. two/browse/DONUT-10', 'entry': datetime.datetime(2016, 6, 6, 6, 7, 8, 123000, tzinfo=tzoffset(None, -25200)), 'jiradescription': None, 'jiraestimate': 1, 'jirafixversion': '1.2.3', 'jiraid': 'DONUT-10', 'jirasummary': 'lkjaldsfjaldf', 'jiraurl': 'two/browse/DONUT-10', 'priority': 'H', 'project': 'DONUT', 'tags': []} self.assertEqual(issue.get_taskwarrior_record(), expected) bugwarrior-1.5.1/tests/test_megaplan.pyc0000644000175000017500000000745013111575063022327 0ustar threebeanthreebean00000000000000ó Âù&Yc@s¸ddlmZddlmZddlZddlZyddlmZWn ek rnejdƒ‚nXddl m Z m Z defd „ƒYZ d e e fd „ƒYZ dS( iÿÿÿÿ(tnext(tobjectN(tMegaplanServices6Upstream python-megaplan does not support python3 yet.i(t ServiceTesttAbstractServiceTesttFakeMegaplanClientcBseZd„Zd„ZRS(cCs ||_dS(N(trecord(tselfR((s7/home/threebean/devel/bugwarrior/tests/test_megaplan.pyt__init__scCs |jgS(N(R(R((s7/home/threebean/devel/bugwarrior/tests/test_megaplan.pytget_actual_taskss(t__name__t __module__RR (((s7/home/threebean/devel/bugwarrior/tests/test_megaplan.pyRs tTestMegaplanIssuecBsseZidd6dd6dd6ZdddgZid d 6d jeƒd 6Zd „Zd„Zd„Zd„Z RS(t somethingsmegaplan.hostnametsomething_elsesmegaplan.logintaljlkjsmegaplan.passwordtonettwotthreei tIdt|tNamecCs?tt|ƒjƒtjdƒ|jtƒ|_WdQXdS(Nsmegaplan.Client(tsuperR tsetUptmocktpatchtget_mock_serviceRtservice(R((s7/home/threebean/devel/bugwarrior/tests/test_megaplan.pyR"scOs1tt|ƒj||Ž}t|jƒ|_|S(N(RR RRtarbitrary_issuetclient(RtargstkwargsR((s7/home/threebean/devel/bugwarrior/tests/test_megaplan.pyR's csìd‰d‰|jj|jƒ}iˆd6|jjd6|jd|j6ˆ|j6|jd|j6}‡fd†}‡fd†}tj j |d tj d tj ƒ-}||d _ ||d _ |j ƒ}WdQX|j||ƒdS( NRshttp://one.com/tprojecttpriorityRiÿÿÿÿcsˆS(N((R(t arbitrary_url(s7/home/threebean/devel/bugwarrior/tests/test_megaplan.pytget_url<scsˆS(N((R(tarbitrary_project(s7/home/threebean/devel/bugwarrior/tests/test_megaplan.pyt get_project?sR%t get_issue_url(Rtget_issue_for_recordRtdefault_priorityt FOREIGN_IDtURLt name_partstTITLERRtmultipletDEFAULTt side_effecttto_taskwarriort assertEqual(Rtissuetexpected_outputR#R%tmockedt actual_output((R$R"s7/home/threebean/devel/bugwarrior/tests/test_megaplan.pyttest_to_taskwarrior-s$     cCsft|jjƒƒ}idd6dd6dd6dd6d d 6d d 6gd 6}|j|jƒ|ƒdS(Ns4(bw)Is#10 - three .. https://something/task/10/card/t descriptioni t megaplanidRt megaplantitleshttps://something/task/10/card/t megaplanurltMR!R R ttags(RRtissuesR1tget_taskwarrior_record(RR2texpected((s7/home/threebean/devel/bugwarrior/tests/test_megaplan.pyt test_issuesKs ( R R tSERVICE_CONFIGR+tjoinRRRR6R@(((s7/home/threebean/devel/bugwarrior/tests/test_megaplan.pyR s    (tbuiltinsRRRtunittesttbugwarrior.services.mplanRt SyntaxErrortSkipTesttbaseRRRR (((s7/home/threebean/devel/bugwarrior/tests/test_megaplan.pyts    bugwarrior-1.5.1/tests/test_trac.py0000644000175000017500000000537313111574702021332 0ustar threebeanthreebean00000000000000from builtins import next from builtins import object from bugwarrior.services.trac import TracService from .base import ServiceTest, AbstractServiceTest class FakeTracTicket(object): @staticmethod def changeLog(issuenumber): return [] class FakeTracServer(object): ticket = FakeTracTicket() class FakeTracLib(object): server = FakeTracServer() def __init__(self, record): self.record = record @staticmethod def query_tickets(query): return ['something'] def get_ticket(self, ticket): return (1, None, None, self.record) class TestTracIssue(AbstractServiceTest, ServiceTest): SERVICE_CONFIG = { 'trac.base_uri': 'http://ljlkajsdfl.com', 'trac.username': 'something', 'trac.password': 'somepwd', } arbitrary_issue = { 'url': 'http://some/url.com/', 'summary': 'Some Summary', 'number': 204, 'priority': 'critical', 'component': 'testcomponent', } def setUp(self): super(TestTracIssue, self).setUp() self.service = self.get_mock_service(TracService) def get_mock_service(self, *args, **kwargs): service = super(TestTracIssue, self).get_mock_service(*args, **kwargs) service.trac = FakeTracLib(self.arbitrary_issue) return service def test_to_taskwarrior(self): arbitrary_extra = { 'annotations': [ 'alpha', 'beta', ], 'project': 'some project', } issue = self.service.get_issue_for_record( self.arbitrary_issue, arbitrary_extra, ) expected_output = { 'project': arbitrary_extra['project'], 'priority': issue.PRIORITY_MAP[self.arbitrary_issue['priority']], 'annotations': arbitrary_extra['annotations'], issue.URL: self.arbitrary_issue['url'], issue.SUMMARY: self.arbitrary_issue['summary'], issue.NUMBER: self.arbitrary_issue['number'], issue.COMPONENT: self.arbitrary_issue['component'], } actual_output = issue.to_taskwarrior() self.assertEquals(actual_output, expected_output) def test_issues(self): issue = next(self.service.issues()) expected = { 'annotations': [], 'description': '(bw)Is#1 - Some Summary .. https://http://ljlkajsdfl.com/ticket/1', 'priority': 'H', 'project': 'unspecified', 'tags': [], 'tracnumber': 1, 'tracsummary': 'Some Summary', 'tracurl': 'https://http://ljlkajsdfl.com/ticket/1', 'traccomponent': 'testcomponent'} self.assertEqual(issue.get_taskwarrior_record(), expected) bugwarrior-1.5.1/tests/test_db.pyc0000644000175000017500000001331513111575063021125 0ustar threebeanthreebean00000000000000ó Âù&Yc@sddlZddlZddlZddlmZddlmZdej fd„ƒYZ defd„ƒYZ d efd „ƒYZ dS( iÿÿÿÿN(tdbi(t ConfigTestt TestMergeLeftcBsGeZd„Zd„Zd„Zd„Zd„Zd„Zd„ZRS(cCsidgd6|_dS(Nttestingt annotations(t issue_dict(tself((s1/home/threebean/devel/bugwarrior/tests/test_db.pytsetUp scKs*tjd||||j||ƒdS(NR(Rt merge_leftt assertEqual(Rtlocaltremotetkwargs((s1/home/threebean/devel/bugwarrior/tests/test_db.pyt assertMergedscCs|ji|jƒdS(N(R R(R((s1/home/threebean/devel/bugwarrior/tests/test_db.pyttest_with_dictscCs#|jtjjiƒ|jƒdS(N(R ttaskwttasktTaskR(R((s1/home/threebean/devel/bugwarrior/tests/test_db.pyttest_with_taskwscCs|j|j|jƒdS(N(R R(R((s1/home/threebean/devel/bugwarrior/tests/test_db.pyttest_already_in_syncscCsMidgd6}tjd|j|dtƒ|jt|jdƒdƒdS(s7 When hamming=False, rough equivalents are duplicated. s testing RthammingiN(RRRtFalseR tlen(RR ((s1/home/threebean/devel/bugwarrior/tests/test_db.pyt!test_rough_equality_hamming_falsescCsMidgd6}tjd|j|dtƒ|jt|jdƒdƒdS(s: When hamming=True, rough equivalents are not duplicated. s testing RRiN(RRRtTrueR R(RR ((s1/home/threebean/devel/bugwarrior/tests/test_db.pyt test_rough_equality_hamming_true$s( t__name__t __module__RR RRRRR(((s1/home/threebean/devel/bugwarrior/tests/test_db.pyR s      tTestSynchronizecBseZd„ZRS(cCsmd„}tjƒ}|jdƒ|jdddƒ|jdƒ|jdddƒtj|jƒ}|j|jƒigd6gd6ƒid d 6d d 6d d6dd6}xˆt dƒD]z}t j t |fƒ|dƒ|j||ƒigd6idd6dd6dd6dd6dd6dd6dd6gd6ƒqºWd |d ‚s( tunittestt configparserR2t taskw.taskRt bugwarriorRtbaseRtTestCaseRRR>(((s1/home/threebean/devel/bugwarrior/tests/test_db.pyts    Vbugwarrior-1.5.1/tests/test_trac.pyc0000644000175000017500000001004213111575063021463 0ustar threebeanthreebean00000000000000ó Âù&Yc@s¥ddlmZddlmZddlmZddlmZmZdefd„ƒYZdefd „ƒYZ d efd „ƒYZ d eefd „ƒYZ dS(iÿÿÿÿ(tnext(tobject(t TracServicei(t ServiceTesttAbstractServiceTesttFakeTracTicketcBseZed„ƒZRS(cCsgS(N((t issuenumber((s3/home/threebean/devel/bugwarrior/tests/test_trac.pyt changeLog s(t__name__t __module__t staticmethodR(((s3/home/threebean/devel/bugwarrior/tests/test_trac.pyRstFakeTracServercBseZeƒZRS((RR Rtticket(((s3/home/threebean/devel/bugwarrior/tests/test_trac.pyR st FakeTracLibcBs2eZeƒZd„Zed„ƒZd„ZRS(cCs ||_dS(N(trecord(tselfR((s3/home/threebean/devel/bugwarrior/tests/test_trac.pyt__init__scCsdgS(Nt something((tquery((s3/home/threebean/devel/bugwarrior/tests/test_trac.pyt query_ticketsscCsddd|jfS(Ni(tNoneR(RR ((s3/home/threebean/devel/bugwarrior/tests/test_trac.pyt get_tickets(RR R tserverRR RR(((s3/home/threebean/devel/bugwarrior/tests/test_trac.pyR s  t TestTracIssuecBspeZidd6dd6dd6Zidd6dd 6d d 6d d 6dd6Zd„Zd„Zd„Zd„ZRS(shttp://ljlkajsdfl.coms trac.base_uriRs trac.usernametsomepwds trac.passwordshttp://some/url.com/turls Some SummarytsummaryiÌtnumbertcriticaltpriorityt testcomponentt componentcCs)tt|ƒjƒ|jtƒ|_dS(N(tsuperRtsetUptget_mock_serviceRtservice(R((s3/home/threebean/devel/bugwarrior/tests/test_trac.pyR!.scOs1tt|ƒj||Ž}t|jƒ|_|S(N(R RR"R tarbitrary_issuettrac(RtargstkwargsR#((s3/home/threebean/devel/bugwarrior/tests/test_trac.pyR"2scCsÇiddgd6dd6}|jj|j|ƒ}i|dd6|j|jdd6|dd6|jd|j6|jd|j6|jd |j6|jd |j6}|jƒ}|j ||ƒdS( Ntalphatbetat annotationss some projecttprojectRRRRR( R#tget_issue_for_recordR$t PRIORITY_MAPtURLtSUMMARYtNUMBERt COMPONENTtto_taskwarriort assertEquals(Rtarbitrary_extratissuetexpected_outputt actual_output((s3/home/threebean/devel/bugwarrior/tests/test_trac.pyttest_to_taskwarrior7s"       cCstt|jjƒƒ}i gd6dd6dd6dd6gd6d d 6d d 6d d6dd6}|j|jƒ|ƒdS(NR*sA(bw)Is#1 - Some Summary .. https://http://ljlkajsdfl.com/ticket/1t descriptiontHRt unspecifiedR+ttagsit tracnumbers Some Summaryt tracsummarys&https://http://ljlkajsdfl.com/ticket/1ttracurlRt traccomponent(RR#tissuest assertEqualtget_taskwarrior_record(RR5texpected((s3/home/threebean/devel/bugwarrior/tests/test_trac.pyt test_issuesRs (RR tSERVICE_CONFIGR$R!R"R8RE(((s3/home/threebean/devel/bugwarrior/tests/test_trac.pyR s     N( tbuiltinsRRtbugwarrior.services.tracRtbaseRRRR R R(((s3/home/threebean/devel/bugwarrior/tests/test_trac.pytsbugwarrior-1.5.1/tests/test_redmine.pyc0000644000175000017500000001025213111575063022160 0ustar threebeanthreebean00000000000000ó Âù&Yc@sƒddlmZddlZddlZddlZddlZddlmZddlm Z m Z de e fd„ƒYZ dS(iÿÿÿÿ(tnextN(tRedMineServicei(t ServiceTesttAbstractServiceTesttTestRedmineIssuecBsueZd'Zidd6dd6dd6Zejjƒjdej j j ƒddƒej d ƒZ ejjƒjdej j j ƒddƒZ i id d 6d d 6d6id d 6d d 6d6e jƒd6dd6dd6dd6dd 6idd 6dd 6d6idd 6dd 6d6id d 6dd 6d6dd 6idd 6d!d 6d"6e jƒd#6Zd$„Zd%„Zejd&„ƒZRS((shttps://somethings redmine.urltsomething_elses redmine.keyt100sredmine.issue_limitttzinfot microsecondiiiÚŠtidsAdam Coddingtontnamet assigned_totauthort created_ons2016-12-30T16:40:29Ztdue_onsThis is a test issue.t descriptiont done_ratioi}itNormaltpriorityiïjsBoiled Cabbage - YumtprojecttNewtstatustBiscuitstsubjecttTaskttrackert updated_oncCs)tt|ƒjƒ|jtƒ|_dS(N(tsuperRtsetUptget_mock_serviceRtservice(tself((s6/home/threebean/devel/bugwarrior/tests/test_redmine.pyR:scsZd‰|jj|jƒ}igd6|jƒd6|jjd6d|j6|jdd|j6|jdd|j6d|j 6|jd|j 6d|j 6d |j 6ˆ|j 6|jd |j6d |j6|j|j6|j|j6|jd |j6d|j6d|j6}‡fd †}tjj|dd|ƒ|jƒ}WdQX|j||ƒdS(Nshttp://lkjlj.comt annotationsRRR R R RRRuTaskR csˆS(N((targs(t arbitrary_url(s6/home/threebean/devel/bugwarrior/tests/test_redmine.pytget_urlXst get_issue_urlt side_effect(Rtget_issue_for_recordtarbitrary_issuetget_project_nametdefault_prioritytNonetDUEDATEt ASSIGNED_TOtAUTHORtCATEGORYt DESCRIPTIONtESTIMATED_HOURStSTATUStURLtSUBJECTtTRACKERtarbitrary_createdt CREATED_ONtarbitrary_updatedt UPDATED_ONtIDt SPENT_HOURSt START_DATEtmocktpatchtobjecttto_taskwarriort assertEqual(Rtissuetexpected_outputR#t actual_output((R"s6/home/threebean/devel/bugwarrior/tests/test_redmine.pyttest_to_taskwarrior>s2            cCs |jddi|jgd6ƒt|jjƒƒ}igd6d|j6dd6dd6d d 6d d 6d|j6d|j6d d6d d6d|j 6|jd|j 6d|j 6d|j 6dd6dd6|j |j6|j|j6dd6gd6}|j|jƒ|ƒdS(Ns'https://something/issues.json?limit=100tjsontissuesR u;(bw)Is#363901 - Biscuits .. https://something/issues/363901RtMRuboiledcabbageyumRi}t redmineidsAdam Coddingtontredmineassignedtot redmineauthorRuBiscuitstredminesubjectuTasktredminetrackeruhttps://something/issues/363901t redmineurlttags(t add_responseR'RRRFR*R+R:R;R.R/R0R1R5R6R7R8R@tget_taskwarrior_record(RRAtexpected((s6/home/threebean/devel/bugwarrior/tests/test_redmine.pyt test_issues`s4         N(t__name__t __module__R*tmaxDifftSERVICE_CONFIGtdatetimetutcnowtreplacetdateutilttzttzutct timedeltaR5R7t isoformatR'RRDt responsestactivateRR(((s6/home/threebean/devel/bugwarrior/tests/test_redmine.pyR sL (         "( tbuiltinsRRWR<RZR_tbugwarrior.services.redmineRtbaseRRR(((s6/home/threebean/devel/bugwarrior/tests/test_redmine.pyts    bugwarrior-1.5.1/tests/test_db.py0000644000175000017500000001250213111574702020756 0ustar threebeanthreebean00000000000000# -*- coding: utf-8 -*- import unittest import configparser as ConfigParser import taskw.task from bugwarrior import db from .base import ConfigTest class TestMergeLeft(unittest.TestCase): def setUp(self): self.issue_dict = {'annotations': ['testing']} def assertMerged(self, local, remote, **kwargs): db.merge_left('annotations', local, remote, **kwargs) self.assertEqual(local, remote) def test_with_dict(self): self.assertMerged({}, self.issue_dict) def test_with_taskw(self): self.assertMerged(taskw.task.Task({}), self.issue_dict) def test_already_in_sync(self): self.assertMerged(self.issue_dict, self.issue_dict) def test_rough_equality_hamming_false(self): """ When hamming=False, rough equivalents are duplicated. """ remote = {'annotations': ['\n testing \n']} db.merge_left('annotations', self.issue_dict, remote, hamming=False) self.assertEqual(len(self.issue_dict['annotations']), 2) def test_rough_equality_hamming_true(self): """ When hamming=True, rough equivalents are not duplicated. """ remote = {'annotations': ['\n testing \n']} db.merge_left('annotations', self.issue_dict, remote, hamming=True) self.assertEqual(len(self.issue_dict['annotations']), 1) class TestSynchronize(ConfigTest): def test_synchronize(self): def get_tasks(tw): tasks = tw.load_tasks() # Remove non-deterministic keys. del tasks['pending'][0]['modified'] del tasks['pending'][0]['entry'] del tasks['pending'][0]['uuid'] return tasks config = ConfigParser.RawConfigParser() config.add_section('general') config.set('general', 'targets', 'my_service') config.add_section('my_service') config.set('my_service', 'service', 'github') tw = taskw.TaskWarrior(self.taskrc) self.assertEqual(tw.load_tasks(), {'completed': [], 'pending': []}) issue = { 'description': 'Blah blah blah. ☃', 'githubtype': 'issue', 'githuburl': 'https://example.com', 'priority': 'M', } # TEST NEW ISSUE AND EXISTING ISSUE. for _ in range(2): db.synchronize(iter((issue,)), config, 'general') self.assertEqual(get_tasks(tw), { 'completed': [], 'pending': [{ u'priority': u'M', u'status': u'pending', u'description': u'Blah blah blah. ☃', u'githuburl': u'https://example.com', u'githubtype': u'issue', u'id': 1, u'urgency': 3.9, }]}) # TEST CHANGED ISSUE. issue['description'] = 'Yada yada yada.' db.synchronize(iter((issue,)), config, 'general') self.assertEqual(get_tasks(tw), { 'completed': [], 'pending': [{ u'priority': u'M', u'status': u'pending', u'description': u'Yada yada yada.', u'githuburl': u'https://example.com', u'githubtype': u'issue', u'id': 1, u'urgency': 3.9, }]}) # TEST CLOSED ISSUE. db.synchronize(iter(()), config, 'general') tasks = tw.load_tasks() # Remove non-deterministic keys. del tasks['completed'][0]['modified'] del tasks['completed'][0]['entry'] del tasks['completed'][0]['end'] del tasks['completed'][0]['uuid'] self.assertEqual(tasks, { 'completed': [{ u'description': u'Yada yada yada.', u'githubtype': u'issue', u'githuburl': u'https://example.com', u'id': 0, u'priority': u'M', u'status': u'completed', u'urgency': 3.9, }], 'pending': []}) class TestUDAs(ConfigTest): def test_udas(self): config = ConfigParser.RawConfigParser() config.add_section('general') config.set('general', 'targets', 'my_service') config.add_section('my_service') config.set('my_service', 'service', 'github') udas = sorted(list(db.get_defined_udas_as_strings(config, 'general'))) self.assertEqual(udas, [ u'uda.githubbody.label=Github Body', u'uda.githubbody.type=string', u'uda.githubcreatedon.label=Github Created', u'uda.githubcreatedon.type=date', u'uda.githubmilestone.label=Github Milestone', u'uda.githubmilestone.type=string', u'uda.githubnumber.label=Github Issue/PR #', u'uda.githubnumber.type=numeric', u'uda.githubrepo.label=Github Repo Slug', u'uda.githubrepo.type=string', u'uda.githubtitle.label=Github Title', u'uda.githubtitle.type=string', u'uda.githubtype.label=Github Type', u'uda.githubtype.type=string', u'uda.githubupdatedat.label=Github Updated', u'uda.githubupdatedat.type=date', u'uda.githuburl.label=Github URL', u'uda.githuburl.type=string', u'uda.githubuser.label=Github User', u'uda.githubuser.type=string', ]) bugwarrior-1.5.1/tests/test_activecollab2.pyc0000644000175000017500000000722213111575063023252 0ustar threebeanthreebean00000000000000ó Âù&Yc@sƒddlmZddlZddlZddlZddlZddlmZddlm Z m Z de e fd„ƒYZ dS(iÿÿÿÿ(tnextN(tActiveCollab2Servicei(t ServiceTesttAbstractServiceTesttTestActiveCollab2IssuecBs,eZidd6dd6dd6dd6Zejjƒejdd ƒjd ejƒZ ejjƒejdd ƒjd ejƒZ i d d 6d d6e j ƒd6dd6dd6dd6dd6e j ƒd6dd6dd6dd6iedd6e d 6gd!6d"d#6Z d$„Zd%„Zejd&„ƒZRS('s http://hellosactivecollab2.urlthowdysactivecollab2.keyisactivecollab2.user_ids 1:one, 2:twosactivecollab2.projectsthoursittzinfoit somethingtprojecttprioritytdue_onshttp://wherever/t permalinki t ticket_idit project_idtTicketttypet created_ont10t created_by_ids Ticket Bodytbodyt Anonymoustnametuser_idtis_ownert assigneessFurther detail.t descriptioncCs)tt|ƒjƒ|jtƒ|_dS(N(tsuperRtsetUptget_mock_serviceRtservice(tself((s</home/threebean/devel/bugwarrior/tests/test_activecollab2.pyR.scCsì|jj|jƒ}i |jdd6|j|jdd6|jd6|jd|j6|jd|j6|jd|j6|jd|j6|j |j 6|jd|j 6|jd |j 6|jd |j 6}|jƒ}|j||ƒdS( NR R tdueR R RRRRR(Rtget_issue_for_recordtarbitrary_issuet PRIORITY_MAPtarbitrary_due_ont PERMALINKt TICKET_IDt PROJECT_IDtTYPEtarbitrary_created_ont CREATED_ONt CREATED_BY_IDtBODYtNAMEtto_taskwarriort assertEqual(Rtissuetexpected_outputt actual_output((s</home/threebean/devel/bugwarrior/tests/test_activecollab2.pyttest_to_taskwarrior2s   cCs×|jtjdƒd|jgƒ|jtjdƒd|jƒt|jjƒƒ}i dd6dd6|jd6d d 6d d 6d d6dd6dd6dd6|jd6dd6dd6gd6}|j |j ƒ|ƒdS(Nsdhttp://hello/\?(?=.*token=howdy)(?=.*path_info=\%2Fprojects\%2F[1-2]\%2Fuser-tasks)(?=.*format=json)tjsonsdhttp://hello/\?(?=.*token=howdy)(?=.*path_info=\%2Fprojects\%2F20\%2Ftickets\%2F10)(?=.*format=json)u Ticket Bodytac2bodyu10tac2createdbyidt ac2createdonu Anonymoustac2nameuhttp://wherever/t ac2permalinkit ac2projectidi t ac2ticketiduTickettac2typeu)(bw)Is#10 - Anonymous .. http://wherever/RR tHR u somethingR ttags( t add_responsetretcompileR"RRtissuesR)R$R/tget_taskwarrior_record(RR0texpected((s</home/threebean/devel/bugwarrior/tests/test_activecollab2.pyt test_issuesHs0       (t__name__t __module__tSERVICE_CONFIGtdatetimetnowt timedeltatreplacetpytztUTCR$R)t isoformattTrueR"RR3t responsestactivateRE(((s</home/threebean/devel/bugwarrior/tests/test_activecollab2.pyR s6 " "      ( tbuiltinsRR@RIRMRQt!bugwarrior.services.activecollab2RtbaseRRR(((s</home/threebean/devel/bugwarrior/tests/test_activecollab2.pyts    bugwarrior-1.5.1/tests/test_bts.pyc0000644000175000017500000000712413111575063021331 0ustar threebeanthreebean00000000000000ó Âù&Yc@s«ddlmZddlmZddlmZddlZddlmZddlmZm Z defd „ƒYZ d efd „ƒYZ d e efd „ƒYZ dS(iÿÿÿÿ(tnext(tstr(tobjectN(tbtsi(t ServiceTesttAbstractServiceTestt FakeBTSBugcBs2eZdZdZdZdZdZdZdZRS(i…^ twnppsiITP: bugwarrior -- Pull tickets from github, bitbucket, bugzilla, jira, trac, and others into taskwarriortwishlistttpending( t__name__t __module__tbug_numtpackagetsubjecttseveritytsourcet forwardedR (((s2/home/threebean/devel/bugwarrior/tests/test_bts.pyR st FakeBTSLibcBseZd„Zd„ZRS(cOsdgS(Ni…^ ((tselftargstkwargs((s2/home/threebean/devel/bugwarrior/tests/test_bts.pytget_bugsscCs|dgkrtgSdS(Ni…^ (R(RR ((s2/home/threebean/devel/bugwarrior/tests/test_bts.pyt get_statuss(R R RR(((s2/home/threebean/devel/bugwarrior/tests/test_bts.pyRs tTestBTSServicecBs=eZdZidd6dd6Zd„Zd„Zd„ZRS(sirl@debian.orgs bts.emailt bugwarriors bts.packagescCs,tt|ƒjƒ|jtjƒ|_dS(N(tsuperRtsetUptget_mock_serviceRt BTSServicetservice(R((s2/home/threebean/devel/bugwarrior/tests/test_bts.pyR)scCsº|jj|jjtƒƒ}i|jtjd6dttjƒ|j6tj |j 6tj|j 6tj |j 6tj|j6tj|j6tj|j6}|jƒ}|j||ƒdS(Ntpriorityshttps://bugs.debian.org/(Rtget_issue_for_recordt_record_for_bugRt PRIORITY_MAPRRR tURLRtSUBJECTtNUMBERRtPACKAGERtSOURCERt FORWARDEDR tSTATUStto_taskwarriort assertEqual(Rtissuetexpected_outputt actual_output((s2/home/threebean/devel/bugwarrior/tests/test_bts.pyttest_to_taskwarrior-s       cCs—tjdtƒƒt|jjƒƒ}WdQXi dd6dd6dd6dd 6d d 6dd 6d d6dd6dd6gd6}|j|jƒ|ƒdS(Ns!bugwarrior.services.bts.debianbtsi…^ t btsnumberR t btsforwardedRt btspackagesiITP: bugwarrior -- Pull tickets from github, bitbucket, bugzilla, jira, trac, and others into taskwarriort btssubjectshttps://bugs.debian.org/810629tbtsurlt btssourceu–(bw)Is#810629 - ITP: bugwarrior -- Pull tickets from github, bitbucket, bugzilla, jira, trac, and others into taskwa .. https://bugs.debian.org/810629t descriptiontLR R t btsstatusutags(tmocktpatchRRRtissuesR,tget_taskwarrior_record(RR-texpected((s2/home/threebean/devel/bugwarrior/tests/test_bts.pyt test_issuesAs N(R R tNonetmaxDifftSERVICE_CONFIGRR0R?(((s2/home/threebean/devel/bugwarrior/tests/test_bts.pyR s   ( tbuiltinsRRRR:tbugwarrior.servicesRtbaseRRRRR(((s2/home/threebean/devel/bugwarrior/tests/test_bts.pyts   bugwarrior-1.5.1/tests/test_activecollab.pyc0000644000175000017500000001266113111575063023173 0ustar threebeanthreebean00000000000000ó Âù&Yc@sµddlmZddlmZddlZddlZddlZddlZddlZddlm Z ddl m Z m Z defd„ƒYZ d e e fd „ƒYZdS( iÿÿÿÿ(tnext(tobjectN(tActiveCollabServicei(t ServiceTesttAbstractServiceTesttFakeActiveCollabLibcBs,eZd„Zd„Zd„Zd„ZRS(cCs ||_dS(N(tarbitrary_issue(tselfR((s;/home/threebean/devel/bugwarrior/tests/test_activecollab.pyt__init__scCs#iii|j|jd6d6d6S(Nttask_idt assignmentst arbitrary_key(R(R((s;/home/threebean/devel/bugwarrior/tests/test_activecollab.pyt get_my_taskss cCsgS(N((R((s;/home/threebean/devel/bugwarrior/tests/test_activecollab.pytget_assignment_labelsscGsgS(N((Rtargs((s;/home/threebean/devel/bugwarrior/tests/test_activecollab.pyt get_commentss(t__name__t __module__RR R R(((s;/home/threebean/devel/bugwarrior/tests/test_activecollab.pyRs   tTestActiveCollabIssuescBs”eZidd6dd6dd6dd6Zejjƒejdd ƒjd ejƒZ ejjƒejdd ƒjd ejƒZ ye j d d ddƒZ Wn ek rÂejdƒ‚nXidd6dd6ie jƒd6d6dd6dd6dd6dd6dd6dd 6ie jƒd6d!6d"d#6e jƒd$6d%d&6d'd(6d d)6dd*6d+d,6d d-6d d.6Zd/„Zd0„Zd1„Zd2„ZRS(3thellosactivecollab.urlthowdysactivecollab.keyt2sactivecollab.user_ids 1:one, 2:twosactivecollab.projectsthoursittzinfois

Ticket Body

tmdtformatthtmlsPandoc is not installed.itpriorityt somethingtprojecttformatted_datetdue_onshttp://wherever/t permalinki R t project_namet project_iditidtissuettypet created_ontTestertcreated_by_nametbodyt AnonymoustnamesSprint 1t milestonetestimated_timet tracked_timetON_HOLDtlabelt assignee_idtlabel_idcCsHtt|ƒjƒd|_tjdƒ|jtƒ|_ WdQXdS(Ns"pyac.library.activeCollab.call_api( tsuperRtsetUptNonetmaxDifftmocktpatchtget_mock_serviceRtservice(R((s;/home/threebean/devel/bugwarrior/tests/test_activecollab.pyR4Ms cOs1tt|ƒj||Ž}t|jƒ|_|S(N(R3RR9RRt activecollab(RRtkwargsR:((s;/home/threebean/devel/bugwarrior/tests/test_activecollab.pyR9Ss cCsbidgd6}|jj|j|ƒ}i|jdd6|jd6dd6|dd6|jd|j6|jd|j6|jd |j6|jd |j6|j|j 6|jd |j 6|jd |j 6|jd |j 6|jd|j 6|jd|j6|jd|j6|jd|j6|jd|j6|jd|j6}|jƒ}|j||ƒdS(Ns an annotationt annotationsRtduetMRR R"R!R%R(R)R+R#R R-R.R,R0(R:tget_issue_for_recordRtarbitrary_due_ont PERMALINKt PROJECT_IDt PROJECT_NAMEtTYPEtarbitrary_created_ont CREATED_ONtCREATED_BY_NAMEtBODYtNAMEt FOREIGN_IDtTASK_IDtESTIMATED_TIMEt TRACKED_TIMEt MILESTONEtLABELtto_taskwarriort assertEqual(Rtarbitrary_extraR$texpected_outputt actual_output((s;/home/threebean/devel/bugwarrior/tests/test_activecollab.pyttest_to_taskwarriorYs2      cCsÇt|jjƒƒ}idd6dd6|jd6dd6dd 6dd 6d d 6d d6dd6dd6dd6dd6dd6dd6gd6dd6|jd6dd6dd6gd 6}|j|jƒ|ƒdS(!Nu Ticket BodytacbodyR'taccreatedbynamet accreatedonitacestimatedtimeitacidtaclabelsSprint 1t acmilestoneR*tacnameshttp://wherever/t acpermalinki t acprojectidRt acprojectnametactaskidt actrackedtimeR$tactypeR=s)(bw)Is#30 - Anonymous .. http://wherever/t descriptionR>R?RRttags(RR:tissuesRFR5RARRtget_taskwarrior_record(RR$texpected((s;/home/threebean/devel/bugwarrior/tests/test_activecollab.pyt test_issuesys.   (RRtSERVICE_CONFIGtdatetimetnowt timedeltatreplacetpytztUTCRARFtpypandoctconvertt_bodytOSErrortunittesttSkipTestt isoformattrstripRR4R9RVRj(((s;/home/threebean/devel/bugwarrior/tests/test_activecollab.pyR sN " "       (tbuiltinsRRRlRvR7RrRpt bugwarrior.services.activecollabRtbaseRRRR(((s;/home/threebean/devel/bugwarrior/tests/test_activecollab.pyts     bugwarrior-1.5.1/tests/test_github.py0000644000175000017500000002437613111574702021667 0ustar threebeanthreebean00000000000000from builtins import next import datetime from unittest import TestCase from configparser import RawConfigParser import pytz import responses from bugwarrior.config import ServiceConfig from bugwarrior.services.github import GithubService, GithubClient from .base import ServiceTest, AbstractServiceTest ARBITRARY_CREATED = ( datetime.datetime.utcnow() - datetime.timedelta(hours=1) ).replace(tzinfo=pytz.UTC, microsecond=0) ARBITRARY_UPDATED = datetime.datetime.utcnow().replace( tzinfo=pytz.UTC, microsecond=0) ARBITRARY_ISSUE = { 'title': 'Hallo', 'html_url': 'https://github.com/arbitrary_username/arbitrary_repo/pull/1', 'url': 'https://api.github.com/repos/arbitrary_username/arbitrary_repo/issues/1', 'number': 10, 'body': 'Something', 'user': {'login': 'arbitrary_login'}, 'milestone': {'title': 'alpha'}, 'labels': [{'name': 'bugfix'}], 'created_at': ARBITRARY_CREATED.isoformat(), 'updated_at': ARBITRARY_UPDATED.isoformat(), 'repo': 'arbitrary_username/arbitrary_repo', } ARBITRARY_EXTRA = { 'project': 'one', 'type': 'issue', 'annotations': [], } class TestGithubIssue(AbstractServiceTest, ServiceTest): maxDiff = None SERVICE_CONFIG = { 'github.login': 'arbitrary_login', 'github.password': 'arbitrary_password', 'github.username': 'arbitrary_username', } def setUp(self): super(TestGithubIssue, self).setUp() self.service = self.get_mock_service(GithubService) def test_normalize_label_to_tag(self): issue = self.service.get_issue_for_record( ARBITRARY_ISSUE, ARBITRARY_EXTRA ) self.assertEqual(issue._normalize_label_to_tag('needs work'), 'needs_work') def test_to_taskwarrior(self): self.service.import_labels_as_tags = True issue = self.service.get_issue_for_record( ARBITRARY_ISSUE, ARBITRARY_EXTRA ) expected_output = { 'project': ARBITRARY_EXTRA['project'], 'priority': self.service.default_priority, 'annotations': [], 'tags': ['bugfix'], issue.URL: ARBITRARY_ISSUE['html_url'], issue.REPO: ARBITRARY_ISSUE['repo'], issue.TYPE: ARBITRARY_EXTRA['type'], issue.TITLE: ARBITRARY_ISSUE['title'], issue.NUMBER: ARBITRARY_ISSUE['number'], issue.UPDATED_AT: ARBITRARY_UPDATED, issue.CREATED_AT: ARBITRARY_CREATED, issue.BODY: ARBITRARY_ISSUE['body'], issue.MILESTONE: ARBITRARY_ISSUE['milestone']['title'], issue.USER: ARBITRARY_ISSUE['user']['login'], } actual_output = issue.to_taskwarrior() self.assertEqual(actual_output, expected_output) @responses.activate def test_issues(self): self.add_response( 'https://api.github.com/user/repos?per_page=100', json=[{ 'name': 'some_repo', 'owner': {'login': 'some_username'} }]) self.add_response( 'https://api.github.com/users/arbitrary_username/repos?per_page=100', json=[{ 'name': 'arbitrary_repo', 'owner': {'login': 'arbitrary_username'} }]) self.add_response( 'https://api.github.com/repos/arbitrary_username/arbitrary_repo/issues?per_page=100', json=[ARBITRARY_ISSUE]) self.add_response( 'https://api.github.com/user/issues?per_page=100', json=[ARBITRARY_ISSUE]) self.add_response( 'https://api.github.com/repos/arbitrary_username/arbitrary_repo/issues/10/comments?per_page=100', json=[{ 'user': {'login': 'arbitrary_login'}, 'body': 'Arbitrary comment.' }]) issue = next(self.service.issues()) expected = { 'annotations': [u'@arbitrary_login - Arbitrary comment.'], 'description': u'(bw)Is#10 - Hallo .. https://github.com/arbitrary_username/arbitrary_repo/pull/1', 'githubbody': u'Something', 'githubcreatedon': ARBITRARY_CREATED, 'githubmilestone': u'alpha', 'githubnumber': 10, 'githubrepo': 'arbitrary_username/arbitrary_repo', 'githubtitle': u'Hallo', 'githubtype': 'issue', 'githubupdatedat': ARBITRARY_UPDATED, 'githuburl': u'https://github.com/arbitrary_username/arbitrary_repo/pull/1', 'githubuser': u'arbitrary_login', 'priority': 'M', 'project': 'arbitrary_repo', 'tags': []} self.assertEqual(issue.get_taskwarrior_record(), expected) class TestGithubIssueQuery(AbstractServiceTest, ServiceTest): maxDiff = None SERVICE_CONFIG = { 'github.login': 'arbitrary_login', 'github.password': 'arbitrary_password', 'github.username': 'arbitrary_username', 'github.query': 'is:open reviewer:octocat', 'github.include_user_repos': 'False', 'github.include_user_issues': 'False', } def setUp(self): super(TestGithubIssueQuery, self).setUp() self.service = self.get_mock_service(GithubService) def test_to_taskwarrior(self): pass @responses.activate def test_issues(self): self.add_response( 'https://api.github.com/search/issues?q=is%3Aopen+reviewer%3Aoctocat&per_page=100', json={'items': [ARBITRARY_ISSUE]}) self.add_response( 'https://api.github.com/repos/arbitrary_username/arbitrary_repo/issues/10/comments?per_page=100', json=[{ 'user': {'login': 'arbitrary_login'}, 'body': 'Arbitrary comment.' }]) issue = list(self.service.issues())[0] expected = { 'annotations': [u'@arbitrary_login - Arbitrary comment.'], 'description': u'(bw)Is#10 - Hallo .. https://github.com/arbitrary_username/arbitrary_repo/pull/1', 'githubbody': u'Something', 'githubcreatedon': ARBITRARY_CREATED, 'githubmilestone': u'alpha', 'githubnumber': 10, 'githubrepo': 'arbitrary_username/arbitrary_repo', 'githubtitle': u'Hallo', 'githubtype': 'issue', 'githubupdatedat': ARBITRARY_UPDATED, 'githuburl': u'https://github.com/arbitrary_username/arbitrary_repo/pull/1', 'githubuser': u'arbitrary_login', 'priority': 'M', 'project': 'arbitrary_repo', 'tags': []} self.assertEqual(issue.get_taskwarrior_record(), expected) class TestGithubService(TestCase): def setUp(self): self.config = RawConfigParser() self.config.interactive = False self.config.add_section('general') self.config.add_section('mygithub') self.config.set('mygithub', 'service', 'github') self.config.set('mygithub', 'github.login', 'tintin') self.config.set('mygithub', 'github.username', 'milou') self.config.set('mygithub', 'github.password', 't0ps3cr3t') self.service_config = ServiceConfig( GithubService.CONFIG_PREFIX, self.config, 'mygithub') def test_token_authorization_header(self): self.config.remove_option('mygithub', 'github.password') self.config.set('mygithub', 'github.token', '@oracle:eval:echo 1234567890ABCDEF') service = GithubService(self.config, 'general', 'mygithub') self.assertEqual(service.client.session.headers['Authorization'], "token 1234567890ABCDEF") def test_default_host(self): """ Check that if github.host is not set, we default to github.com """ service = GithubService(self.config, 'general', 'mygithub') self.assertEquals("github.com", service.host) def test_overwrite_host(self): """ Check that if github.host is set, we use its value as host """ self.config.set('mygithub', 'github.host', 'github.example.com') service = GithubService(self.config, 'general', 'mygithub') self.assertEquals("github.example.com", service.host) def test_keyring_service(self): """ Checks that the keyring service name """ keyring_service = GithubService.get_keyring_service(self.service_config) self.assertEquals("github://tintin@github.com/milou", keyring_service) def test_keyring_service_host(self): """ Checks that the keyring key depends on the github host. """ self.config.set('mygithub', 'github.host', 'github.example.com') keyring_service = GithubService.get_keyring_service(self.service_config) self.assertEquals("github://tintin@github.example.com/milou", keyring_service) def test_get_repository_from_issue_url__issue(self): issue = dict(repos_url="https://github.com/foo/bar") repository = GithubService.get_repository_from_issue(issue) self.assertEquals("foo/bar", repository) def test_get_repository_from_issue_url__pull_request(self): issue = dict(repos_url="https://github.com/foo/bar") repository = GithubService.get_repository_from_issue(issue) self.assertEquals("foo/bar", repository) def test_get_repository_from_issue__enterprise_github(self): issue = dict(repos_url="https://github.acme.biz/foo/bar") repository = GithubService.get_repository_from_issue(issue) self.assertEquals("foo/bar", repository) class TestGithubClient(TestCase): def test_api_url(self): auth = {'token': 'xxxx'} client = GithubClient('github.com', auth) self.assertEquals( client._api_url('/some/path'), 'https://api.github.com/some/path') def test_api_url_with_context(self): auth = {'token': 'xxxx'} client = GithubClient('github.com', auth) self.assertEquals( client._api_url('/some/path/{foo}', foo='bar'), 'https://api.github.com/some/path/bar') def test_api_url_with_custom_host(self): """ Test generating an API URL with a custom host """ auth = {'token': 'xxxx'} client = GithubClient('github.example.com', auth) self.assertEquals( client._api_url('/some/path'), 'https://github.example.com/api/v3/some/path') bugwarrior-1.5.1/tests/test_activecollab.py0000644000175000017500000001132613111574702023024 0ustar threebeanthreebean00000000000000from builtins import next from builtins import object import datetime import unittest import mock import pypandoc import pytz from bugwarrior.services.activecollab import ( ActiveCollabService ) from .base import ServiceTest, AbstractServiceTest class FakeActiveCollabLib(object): def __init__(self, arbitrary_issue): self.arbitrary_issue = arbitrary_issue def get_my_tasks(self): return {'arbitrary_key': {'assignments': { self.arbitrary_issue['task_id']: self.arbitrary_issue}}} def get_assignment_labels(self): return [] def get_comments(self, *args): return [] class TestActiveCollabIssues(AbstractServiceTest, ServiceTest): SERVICE_CONFIG = { 'activecollab.url': 'hello', 'activecollab.key': 'howdy', 'activecollab.user_id': '2', 'activecollab.projects': '1:one, 2:two' } arbitrary_due_on = ( datetime.datetime.now() - datetime.timedelta(hours=1) ).replace(tzinfo=pytz.UTC) arbitrary_created_on = ( datetime.datetime.now() - datetime.timedelta(hours=2) ).replace(tzinfo=pytz.UTC) try: _body = pypandoc.convert('

Ticket Body

', 'md', format='html') except OSError: raise unittest.SkipTest('Pandoc is not installed.') arbitrary_issue = { 'priority': 0, 'project': 'something', 'due_on': { 'formatted_date': arbitrary_due_on.isoformat(), }, 'permalink': 'http://wherever/', 'task_id': 10, 'project_name': 'something', 'project_id': 10, 'id': 30, 'type': 'issue', 'created_on': { 'formatted_date': arbitrary_created_on.isoformat(), }, 'created_by_name': 'Tester', 'body': _body.rstrip(), 'name': 'Anonymous', 'milestone': 'Sprint 1', 'estimated_time': 1, 'tracked_time': 10, 'label': 'ON_HOLD', 'assignee_id': 2, 'label_id': 1, } def setUp(self): super(TestActiveCollabIssues, self).setUp() self.maxDiff = None with mock.patch('pyac.library.activeCollab.call_api'): self.service = self.get_mock_service(ActiveCollabService) def get_mock_service(self, *args, **kwargs): service = super(TestActiveCollabIssues, self).get_mock_service( *args, **kwargs) service.activecollab = FakeActiveCollabLib(self.arbitrary_issue) return service def test_to_taskwarrior(self): arbitrary_extra = { 'annotations': ['an annotation'], } issue = self.service.get_issue_for_record( self.arbitrary_issue, arbitrary_extra) expected_output = { 'project': self.arbitrary_issue['project'], 'due': self.arbitrary_due_on, 'priority': 'M', 'annotations': arbitrary_extra['annotations'], issue.PERMALINK: self.arbitrary_issue['permalink'], issue.PROJECT_ID: self.arbitrary_issue['project_id'], issue.PROJECT_NAME: self.arbitrary_issue['project_name'], issue.TYPE: self.arbitrary_issue['type'], issue.CREATED_ON: self.arbitrary_created_on, issue.CREATED_BY_NAME: self.arbitrary_issue['created_by_name'], issue.BODY: self.arbitrary_issue['body'], issue.NAME: self.arbitrary_issue['name'], issue.FOREIGN_ID: self.arbitrary_issue['id'], issue.TASK_ID: self.arbitrary_issue['task_id'], issue.ESTIMATED_TIME: self.arbitrary_issue['estimated_time'], issue.TRACKED_TIME: self.arbitrary_issue['tracked_time'], issue.MILESTONE: self.arbitrary_issue['milestone'], issue.LABEL: self.arbitrary_issue['label'], } actual_output = issue.to_taskwarrior() self.assertEqual(actual_output, expected_output) def test_issues(self): issue = next(self.service.issues()) expected = { 'acbody': u'Ticket Body', 'accreatedbyname': 'Tester', 'accreatedon': self.arbitrary_created_on, 'acestimatedtime': 1, 'acid': 30, 'aclabel': None, 'acmilestone': 'Sprint 1', 'acname': 'Anonymous', 'acpermalink': 'http://wherever/', 'acprojectid': 10, 'acprojectname': 'something', 'actaskid': 10, 'actrackedtime': 10, 'actype': 'issue', 'annotations': [], 'description': '(bw)Is#30 - Anonymous .. http://wherever/', 'due': self.arbitrary_due_on, 'priority': 'M', 'project': 'something', 'tags': []} self.assertEqual(issue.get_taskwarrior_record(), expected) bugwarrior-1.5.1/tests/test_teamlab.pyc0000644000175000017500000000571013111575063022145 0ustar threebeanthreebean00000000000000ó Âù&Yc@skddlmZddlZddlZddlmZddlmZmZdeefd„ƒYZ dS(iÿÿÿÿ(tnextN(tTeamLabServicei(t ServiceTesttAbstractServiceTesttTestTeamlabIssuecBsweZidd6dd6dd6dd6Zidd 6d d 6id d 6d 6dd6Zd„Zd„Zejd„ƒZRS(t somethingsteamlab.hostnametalkjdsfs teamlab.logintlkjkljsteamlab.passwordtabcdefsteamlab.project_nametHellottitlei tidiŒt projectOwneritstatuscCs?tt|ƒjƒtjdƒ|jtƒ|_WdQXdS(Ns6bugwarrior.services.teamlab.TeamLabClient.authenticate(tsuperRtsetUptmocktpatchtget_mock_serviceRtservice(tself((s6/home/threebean/devel/bugwarrior/tests/test_teamlab.pyRs csÎd‰|jj|jƒ}i|jdd6|jjd6|jd|j6|jd|j6ˆ|j6|jdd|j6}‡fd†}t j j |d d |ƒ|j ƒ}WdQX|j ||ƒdS( Nshttp://galkjsdflkj.com/steamlab.project_nametprojecttpriorityR R R csˆS(N((targs(t arbitrary_url(s6/home/threebean/devel/bugwarrior/tests/test_teamlab.pytget_url/st get_issue_urlt side_effect(Rtget_issue_for_recordtarbitrary_issuetSERVICE_CONFIGtdefault_prioritytTITLEt FOREIGN_IDtURLtPROJECTOWNER_IDRRtobjecttto_taskwarriort assertEqual(Rtissuetexpected_outputRt actual_output((Rs6/home/threebean/devel/bugwarrior/tests/test_teamlab.pyttest_to_taskwarrior!s  cCs†|jdd|jgƒt|jjƒƒ}idd6dd6dd6gd 6d d 6d d 6dd6dd6}|j|jƒ|ƒdS(Ns0http://something/api/1.0/project/task/@self.jsontjsonuR(bw)Is#10 - Hello .. http://something/products/projects/tasks.aspx?prjID=140&id=10t descriptiontMRRRttagsi t teamlabidiŒtteamlabprojectowneriduHellot teamlabtitles=http://something/products/projects/tasks.aspx?prjID=140&id=10t teamlaburl(t add_responseRRRtissuesR&tget_taskwarrior_record(RR'texpected((s6/home/threebean/devel/bugwarrior/tests/test_teamlab.pyt test_issues7s  ( t__name__t __module__RRRR*t responsestactivateR7(((s6/home/threebean/devel/bugwarrior/tests/test_teamlab.pyR s     ( tbuiltinsRRR:tbugwarrior.services.teamlabRtbaseRRR(((s6/home/threebean/devel/bugwarrior/tests/test_teamlab.pyts   bugwarrior-1.5.1/tests/test_data.py0000644000175000017500000000204713111574702021305 0ustar threebeanthreebean00000000000000import os import json import configparser from bugwarrior import data from .base import ConfigTest class TestData(ConfigTest): def setUp(self): super(TestData, self).setUp() config = configparser.RawConfigParser() config.add_section('general') self.data = data.BugwarriorData(self.lists_path) def assert0600(self): permissions = oct(os.stat(self.data.datafile).st_mode & 0o777) # python2 -> 0600, python3 -> 0o600 self.assertIn(permissions, ['0600', '0o600']) def test_get_set(self): # "touch" data file. with open(self.data.datafile, 'w+') as handle: json.dump({'old': 'stuff'}, handle) self.data.set('key', 'value') self.assertEqual(self.data.get('key'), 'value') self.assertEqual( self.data.get_data(), {'old': 'stuff', 'key': 'value'}) self.assert0600() def test_set_first_time(self): self.data.set('key', 'value') self.assertEqual(self.data.get('key'), 'value') self.assert0600() bugwarrior-1.5.1/tests/test_bitbucket.pyc0000644000175000017500000001214513111575063022514 0ustar threebeanthreebean00000000000000ó Âù&Yc@s_ddlmZddlZddlmZddlmZmZdeefd„ƒYZdS(iÿÿÿÿ(tunicode_literalsN(tBitbucketServicei(t ServiceTesttAbstractServiceTesttTestBitbucketIssuecBskeZidd6dd6dd6Zd„Zd„Zejd„ƒZd „Zd „Z ejd „ƒZ RS( u somethingubitbucket.loginusomenameubitbucket.usernameusomething elseubitbucket.passwordcCs)tt|ƒjƒ|jtƒ|_dS(N(tsuperRtsetUptget_mock_serviceRtservice(tself((s8/home/threebean/devel/bugwarrior/tests/test_bitbucket.pyRscCsÆidd6dd6dd6}idd6d d 6d gd 6}|jj||ƒ}i|d d 6|j|dd6|d d 6|d|j6|d|j6|d|j6}|jƒ}|j||ƒdS( Nutrivialupriorityu100uidu Some Titleutitleuhttp://hello-there.com/uurlu SomethinguprojectuOneu annotations(Rtget_issue_for_recordt PRIORITY_MAPtURLt FOREIGN_IDtTITLEtto_taskwarriort assertEqual(R tarbitrary_issuetarbitrary_extratissuetexpected_outputt actual_output((s8/home/threebean/devel/bugwarrior/tests/test_bitbucket.pyttest_to_taskwarriors&       c Cs÷|jddiidd6td6gd6ƒ|jddiidd 6d d 6iid d 6d6d6dd6gd6ƒ|jddiidd6d6dd6gƒ|jddiidd 6d d6iid d 6d6d6dd6gd6ƒ|jddiiidd6d6idd6d6gd6ƒg|jjƒD] }|^q-\}}idgd6dd 6dd!6d d"6d#d$6d%d&6d'd(6gd)6}|j|jƒ|ƒidgd6dd 6dd!6d*d"6d+d$6d%d&6d'd(6gd)6}|j|jƒ|ƒdS(,Nu4https://api.bitbucket.org/2.0/repositories/somename/tjsonusomename/somerepou full_nameu has_issuesuvaluesuDhttps://api.bitbucket.org/2.0/repositories/somename/somerepo/issues/uSome Bugutitleuopenustatusu example.comuhrefuhtmlulinksiuiduNhttps://api.bitbucket.org/1.0/repositories/somename/somerepo/issues/1/commentsunobodyuusernameu author_infou Some comment.ucontentuJhttps://api.bitbucket.org/2.0/repositories/somename/somerepo/pullrequests/u Some FeatureustateuThttps://api.bitbucket.org/2.0/repositories/somename/somerepo/pullrequests/1/commentsuuserurawu@nobody - Some comment.u annotationsu bitbucketidubitbuckettitleu bitbucketurlu"(bw)Is#1 - Some Bug .. example.comu descriptionuMupriorityusomerepouprojectutagsuhttps://bitbucket.org/u1(bw)Is#1 - Some Feature .. https://bitbucket.org/(t add_responsetTrueRtissuesRtget_taskwarrior_record(R tiRtprtexpected_issuet expected_pr((s8/home/threebean/devel/bugwarrior/tests/test_bitbucket.pyt test_issues4sd(    cCsAidd6idd6d6}|j|jjd|fƒdƒdS(NuFoobarutitleutintinuusernameuassigneeufoo(RRt get_owner(R R((s8/home/threebean/devel/bugwarrior/tests/test_bitbucket.pyttest_get_ownerwscCs7idd6dd6}|j|jjd|fƒƒdS(NuFoobarutitleuassigneeufoo(tNonet assertIsNoneRR!(R R((s8/home/threebean/devel/bugwarrior/tests/test_bitbucket.pyttest_get_owner_none~s c Cs-|jddiidd6dd6iidd6d 6d 6d d 6gd 6dd6ƒ|jddiidd6dd6iidd6d 6d 6dd 6gd 6ƒt|jjdƒƒ}didd6dd6iidd6d 6d 6d d 6fdidd6dd6iidd6d 6d 6dd 6fg}|j||ƒdS(NuDhttps://api.bitbucket.org/2.0/repositories/somename/somerepo/issues/RuSome Bugutitleuopenustatusu example.comuhrefuhtmlulinksiuiduvaluesuKhttps://api.bitbucket.org/2.0/repositories/somename/somerepo/issues/?page=2unextuSome Other Bugiusomename/somerepo(RtlistRt fetch_issuesR(R Rtexpected((s8/home/threebean/devel/bugwarrior/tests/test_bitbucket.pyttest_fetch_issues_pagination…s:  ( t__name__t __module__tSERVICE_CONFIGRRt responsestactivateR R"R%R)(((s8/home/threebean/devel/bugwarrior/tests/test_bitbucket.pyR s   C  ( t __future__RR-tbugwarrior.services.bitbucketRtbaseRRR(((s8/home/threebean/devel/bugwarrior/tests/test_bitbucket.pyts bugwarrior-1.5.1/tests/test_teamlab.py0000644000175000017500000000446113111574702022003 0ustar threebeanthreebean00000000000000from builtins import next import mock import responses from bugwarrior.services.teamlab import TeamLabService from .base import ServiceTest, AbstractServiceTest class TestTeamlabIssue(AbstractServiceTest, ServiceTest): SERVICE_CONFIG = { 'teamlab.hostname': 'something', 'teamlab.login': 'alkjdsf', 'teamlab.password': 'lkjklj', 'teamlab.project_name': 'abcdef', } arbitrary_issue = { 'title': 'Hello', 'id': 10, 'projectOwner': { 'id': 140, }, 'status': 1, } def setUp(self): super(TestTeamlabIssue, self).setUp() with mock.patch( 'bugwarrior.services.teamlab.TeamLabClient.authenticate' ): self.service = self.get_mock_service(TeamLabService) def test_to_taskwarrior(self): arbitrary_url = 'http://galkjsdflkj.com/' issue = self.service.get_issue_for_record(self.arbitrary_issue) expected_output = { 'project': self.SERVICE_CONFIG['teamlab.project_name'], 'priority': self.service.default_priority, issue.TITLE: self.arbitrary_issue['title'], issue.FOREIGN_ID: self.arbitrary_issue['id'], issue.URL: arbitrary_url, issue.PROJECTOWNER_ID: self.arbitrary_issue['projectOwner']['id'] } def get_url(*args): return arbitrary_url with mock.patch.object(issue, 'get_issue_url', side_effect=get_url): actual_output = issue.to_taskwarrior() self.assertEqual(actual_output, expected_output) @responses.activate def test_issues(self): self.add_response( 'http://something/api/1.0/project/task/@self.json', json=[self.arbitrary_issue]) issue = next(self.service.issues()) expected = { 'description': u'(bw)Is#10 - Hello .. http://something/products/projects/tasks.aspx?prjID=140&id=10', 'priority': 'M', 'project': 'abcdef', 'tags': [], 'teamlabid': 10, 'teamlabprojectownerid': 140, 'teamlabtitle': u'Hello', 'teamlaburl': 'http://something/products/projects/tasks.aspx?prjID=140&id=10'} self.assertEqual(issue.get_taskwarrior_record(), expected) bugwarrior-1.5.1/tests/test_youtrak.pyc0000644000175000017500000001033513111575063022235 0ustar threebeanthreebean00000000000000ó Âù&Yc@s±ddlmZejƒddlmZddlZddlZddlmZddl m Z ddl m Z m Z mZde fd „ƒYZd ee fd „ƒYZdS( iÿÿÿÿ(tstandard_library(tnextN(t ServiceConfig(tYoutrackServicei(t ConfigTestt ServiceTesttAbstractServiceTesttTestYoutrackServicecBseZd„Zd„ZRS(cCsrtt|ƒjƒtjƒ|_|jjdƒ|jjdƒ|jjdddƒ|jjdddƒdS(Ntgeneralt myservicesyoutrack.logintfoobarsyoutrack.passwordtXXXXXX(tsuperRtsetUpt configparsertRawConfigParsertconfigt add_sectiontset(tself((s6/home/threebean/devel/bugwarrior/tests/test_youtrak.pyR s cCsK|jjdddƒttj|jdƒ}|jtj|ƒdƒdS(NR s youtrack.hostsyoutrack.example.coms&youtrack://foobar@youtrack.example.com(RRRRt CONFIG_PREFIXt assertEqualtget_keyring_service(Rtservice_config((s6/home/threebean/devel/bugwarrior/tests/test_youtrak.pyttest_get_keyring_services  (t__name__t __module__R R(((s6/home/threebean/devel/bugwarrior/tests/test_youtrak.pyR s tTestYoutrackIssuecBs¼eZdZidd6dd6dd6ed6Zidd6id d 6d d 6id d 6dd 6idd 6dd 6gd6idd 6idd 6gd6ZiZd„Zd„Z e j d„ƒZ RS(syoutrack.example.coms youtrack.hosttarbitrary_loginsyoutrack.logintarbitrary_passwordsyoutrack.passwordsyoutrack.anonymoussTEST-1tidtprojectShortNametnametTESTtvaluetnumberInProjectt1tsummarys Hello Worldtfieldtbugs New FeaturettagcCs)tt|ƒjƒ|jtƒ|_dS(N(R RR tget_mock_serviceRtservice(R((s6/home/threebean/devel/bugwarrior/tests/test_youtrak.pyR BscCs t|j_|jj|j|jƒ}idd6|jjd6ddgd6d|j6d|j6d |j 6d|j 6d |j 6}|j ƒ}|j ||ƒdS( NR!tprojecttpriorityubugu new_featurettagssTEST-1s Hello Worlds-https://youtrack.example.com:443/issue/TEST-1i(tTrueR*t import_tagstget_issue_for_recordtarbitrary_issuetarbitrary_extratdefault_prioritytISSUEtSUMMARYtURLtPROJECTtNUMBERtto_taskwarriorR(Rtissuetexpected_outputt actual_output((s6/home/threebean/devel/bugwarrior/tests/test_youtrak.pyttest_to_taskwarriorFs         cCs |jddi|jgd6ƒt|jjƒƒ}i dd6dd6|jjd6d d gd 6d d 6dd6dd6dd6dd6}|j|jƒ|ƒdS(NsQhttps://youtrack.example.com:443/rest/issue?filter=for%3Ame+%23Unresolved&max=100tjsonR:uL(bw)Is#TEST-1 - Hello World .. https://youtrack.example.com:443/issue/TEST-1t descriptionR!R+R,ubugu new_featureR-sTEST-1t youtrackissues Hello Worldtyoutracksummarys-https://youtrack.example.com:443/issue/TEST-1t youtrackurltyoutrackprojectityoutracknumber(t add_responseR1RR*tissuesR3Rtget_taskwarrior_record(RR:texpected((s6/home/threebean/devel/bugwarrior/tests/test_youtrak.pyt test_issuesXs   N( RRtNonetmaxDiffR.tSERVICE_CONFIGR1R2R R=t responsestactivateRI(((s6/home/threebean/devel/bugwarrior/tests/test_youtrak.pyRs2   (tfutureRtinstall_aliasestbuiltinsRRRMtbugwarrior.servicesRtbugwarrior.services.youtrackRtbaseRRRRR(((s6/home/threebean/devel/bugwarrior/tests/test_youtrak.pyts   bugwarrior-1.5.1/tests/__init__.pyc0000644000175000017500000000021112734015116021225 0ustar threebeanthreebean00000000000000ó ¿(ETc@sdS(N((((s2/home/threebean/devel/bugwarrior/tests/__init__.pytsbugwarrior-1.5.1/tests/test_bts.py0000644000175000017500000000523013111574702021161 0ustar threebeanthreebean00000000000000from builtins import next from builtins import str from builtins import object import mock from bugwarrior.services import bts from .base import ServiceTest, AbstractServiceTest class FakeBTSBug(object): bug_num = 810629 package = "wnpp" subject = ("ITP: bugwarrior -- Pull tickets from github, " "bitbucket, bugzilla, jira, trac, and others into " "taskwarrior") severity = "wishlist" source = "" forwarded = "" pending = "pending" class FakeBTSLib(object): def get_bugs(self, *args, **kwargs): return [810629] def get_status(self, bug_num): if bug_num == [810629]: return [FakeBTSBug] class TestBTSService(AbstractServiceTest, ServiceTest): maxDiff = None SERVICE_CONFIG = { 'bts.email': 'irl@debian.org', 'bts.packages': 'bugwarrior', } def setUp(self): super(TestBTSService, self).setUp() self.service = self.get_mock_service(bts.BTSService) def test_to_taskwarrior(self): issue = self.service.get_issue_for_record( self.service._record_for_bug(FakeBTSBug) ) expected_output = { 'priority': issue.PRIORITY_MAP[FakeBTSBug.severity], issue.URL: "https://bugs.debian.org/" + str(FakeBTSBug.bug_num), issue.SUBJECT: FakeBTSBug.subject, issue.NUMBER: FakeBTSBug.bug_num, issue.PACKAGE: FakeBTSBug.package, issue.SOURCE: FakeBTSBug.source, issue.FORWARDED: FakeBTSBug.forwarded, issue.STATUS: FakeBTSBug.pending, } actual_output = issue.to_taskwarrior() self.assertEqual(actual_output, expected_output) def test_issues(self): with mock.patch('bugwarrior.services.bts.debianbts', FakeBTSLib()): issue = next(self.service.issues()) expected = { 'btsnumber': 810629, 'btsforwarded': '', 'btspackage': 'wnpp', 'btssubject': ('ITP: bugwarrior -- Pull tickets from github, ' 'bitbucket, bugzilla, jira, trac, and others into ' 'taskwarrior'), 'btsurl': 'https://bugs.debian.org/810629', 'btssource': '', 'description': (u'(bw)Is#810629 - ITP: bugwarrior -- Pull tickets' u' from github, bitbucket, bugzilla, jira, trac, ' u'and others into taskwa .. https://bugs.debian.o' u'rg/810629'), 'priority': 'L', 'btsstatus': 'pending', u'tags': []} self.assertEqual(issue.get_taskwarrior_record(), expected) bugwarrior-1.5.1/tests/test_jira.pyc0000644000175000017500000001072613111575063021470 0ustar threebeanthreebean00000000000000ó Âù&Yc@s«ddlmZddlmZddlmZddlZddlmZmZddl m Z ddl m Z m Z d efd „ƒYZd e e fd „ƒYZdS( iÿÿÿÿ(tnext(tobject(t namedtupleN(ttzoffsettdatetime(t JiraServicei(t ServiceTesttAbstractServiceTesttFakeJiraClientcBs#eZd„Zd„Zd„ZRS(cCs ||_dS(N(tarbitrary_record(tselfR ((s3/home/threebean/devel/bugwarrior/tests/test_jira.pyt__init__ scOs/tdddgƒ}||j|jdƒgS(NtCasetrawtkey(RR (R targstkwargsR ((s3/home/threebean/devel/bugwarrior/tests/test_jira.pyt search_issuesscOsdS(N(tNone(R RR((s3/home/threebean/devel/bugwarrior/tests/test_jira.pytcommentss(t__name__t __module__R RR(((s3/home/threebean/devel/bugwarrior/tests/test_jira.pyR s  t TestJiraIssuecBsªeZidd6dd6dd6ZdZdZdZd Ziid d 6ed 6ed 6dd6idd6gd6d6deefd6Zd„Zd„Z d„Z d„Z RS(tones jira.usernamettwos jira.base_uritthrees jira.passwordit10tDONUTt lkjaldsfjaldftBlockertprioritytsummaryt timeestimates2016-06-06T06:07:08.123-0700tcreateds1.2.3tnamet fixVersionstfieldss%s-%sRcCs?tt|ƒjƒtjdƒ|jtƒ|_WdQXdS(Nsjira.client.JIRA._get_json(tsuperRtsetUptmocktpatchtget_mock_serviceRtservice(R ((s3/home/threebean/devel/bugwarrior/tests/test_jira.pyR&/scOs1tt|ƒj||Ž}t|jƒ|_|S(N(R%RR)RR tjira(R RRR*((s3/home/threebean/devel/bugwarrior/tests/test_jira.pyR)4sc s@d‰idd6dgd6}|jj|j|ƒ}i |jd6|j|jddd6|dd6gd 6tjd d d d d d ddtddƒƒd6dd6ˆ|j6|jd|j 6|j |j 6d|j 6|j dd|j6}‡fd†}tjj|dd|ƒ|jƒ}WdQX|j||ƒdS(Ns http://oneit jira_versions an annotationt annotationstprojectR$Rttagsiàiiiixàttzinfoiÿÿtentrys1.2.3tjirafixversionRi<csˆS(N((R(t arbitrary_url(s3/home/threebean/devel/bugwarrior/tests/test_jira.pytget_urlUsR4t side_effect(R*tget_issue_for_recordR tarbitrary_projectt PRIORITY_MAPRRRtURLt FOREIGN_IDtarbitrary_summarytSUMMARYt DESCRIPTIONtarbitrary_estimationtESTIMATER'R(Rtto_taskwarriort assertEqual(R tarbitrary_extratissuetexpected_outputR4t actual_output((R3s3/home/threebean/devel/bugwarrior/tests/test_jira.pyttest_to_taskwarrior9s,    1   c Cs³t|jjƒƒ}i gd6dd6tjdddddddd tdd ƒƒd 6dd 6d d6dd6dd6dd6dd6dd6dd6gd6}|j|jƒ|ƒdS(NR-s0(bw)Is#10 - lkjaldsfjaldf .. two/browse/DONUT-10t descriptioniàiiiixàR0iÿÿR1tjiradescriptionit jiraestimates1.2.3R2sDONUT-10tjiraidRt jirasummarystwo/browse/DONUT-10tjiraurltHRRR.R/(RR*tissuesRRRRAtget_taskwarrior_record(R RCtexpected((s3/home/threebean/devel/bugwarrior/tests/test_jira.pyt test_issues]s1 ( RRtSERVICE_CONFIGR>t arbitrary_idR7R;R R&R)RFRQ(((s3/home/threebean/devel/bugwarrior/tests/test_jira.pyRs(    $(tbuiltinsRRt collectionsRR't dateutil.tzRRtbugwarrior.services.jiraRtbaseRRRR(((s3/home/threebean/devel/bugwarrior/tests/test_jira.pyts  bugwarrior-1.5.1/tests/test_service.py0000644000175000017500000000551213111574702022034 0ustar threebeanthreebean00000000000000import unittest from bugwarrior import config, services LONG_MESSAGE = """\ Some message that is over 100 characters. This message is so long it's going to fill up your floppy disk taskwarrior backup. Actually it's not that long.""".replace('\n', ' ') class TestIssueService(unittest.TestCase): def setUp(self): super(TestIssueService, self).setUp() self.config = config.BugwarriorConfigParser() self.config.add_section('general') def test_build_annotations_default(self): service = services.IssueService(self.config, 'general', 'test') annotations = service.build_annotations( (('some_author', LONG_MESSAGE),), 'example.com') self.assertEqual(annotations, [ u'@some_author - Some message that is over 100 characters. Thi...' ]) def test_build_annotations_limited(self): self.config.set('general', 'annotation_length', '20') service = services.IssueService(self.config, 'general', 'test') annotations = service.build_annotations( (('some_author', LONG_MESSAGE),), 'example.com') self.assertEqual( annotations, [u'@some_author - Some message that is...']) def test_build_annotations_limitless(self): self.config.set('general', 'annotation_length', '') service = services.IssueService(self.config, 'general', 'test') annotations = service.build_annotations( (('some_author', LONG_MESSAGE),), 'example.com') self.assertEqual(annotations, [ u'@some_author - {message}'.format(message=LONG_MESSAGE)]) class TestIssue(unittest.TestCase): def setUp(self): super(TestIssue, self).setUp() self.config = config.BugwarriorConfigParser() self.config.add_section('general') def makeIssue(self): service = services.IssueService(self.config, 'general', 'test') service.ISSUE_CLASS = services.Issue return service.get_issue_for_record(None) def test_build_default_description_default(self): issue = self.makeIssue() description = issue.build_default_description(LONG_MESSAGE) self.assertEqual( description, u'(bw)Is# - Some message that is over 100 chara') def test_build_default_description_limited(self): self.config.set('general', 'description_length', '20') issue = self.makeIssue() description = issue.build_default_description(LONG_MESSAGE) self.assertEqual( description, u'(bw)Is# - Some message that is') def test_build_default_description_limitless(self): self.config.set('general', 'description_length', '') issue = self.makeIssue() description = issue.build_default_description(LONG_MESSAGE) self.assertEqual( description, u'(bw)Is# - {message}'.format(message=LONG_MESSAGE)) bugwarrior-1.5.1/tests/test_trello.py0000644000175000017500000002155613111574702021703 0ustar threebeanthreebean00000000000000from __future__ import unicode_literals, print_function from future import standard_library standard_library.install_aliases() from builtins import next import configparser from mock import patch import responses from bugwarrior.config import ServiceConfig from bugwarrior.services.trello import TrelloService, TrelloIssue from .base import ConfigTest, ServiceTest class TestTrelloIssue(ServiceTest): JSON = { "id": "542bbb6583d705eb05bbe491", "idShort": 42, "name": "So long, and thanks for all the fish!", "shortLink": "AAaaBBbb", "shortUrl": "https://trello.com/c/AAaaBBbb", "url": "https://trello.com/c/AAaBBbb/42-so-long", "labels": [{'name': "foo"}, {"name": "bar"}], } def setUp(self): super(TestTrelloIssue, self).setUp() origin = dict(inline_links=True, description_length=31, import_labels_as_tags=True) extra = {'boardname': 'Hyperspatial express route', 'listname': 'Something'} self.issue = TrelloIssue(self.JSON, origin, extra) def test_default_description(self): """ Test the generated description """ expected_desc = "(bw)#42 - So long, and thanks for all the" \ " .. https://trello.com/c/AAaaBBbb" self.assertEqual(expected_desc, self.issue.get_default_description()) def test_to_taskwarrior__project(self): """ By default, the project is the board name """ expected_project = "Hyperspatial express route" self.assertEqual(expected_project, self.issue.to_taskwarrior().get('project', None)) class TestTrelloService(ConfigTest): BOARD = {'id': 'B04RD', 'name': 'My Board'} CARD1 = {'id': 'C4RD', 'name': 'Card 1', 'members': [{'username': 'tintin'}], 'idShort': 1, 'shortLink': 'abcd', 'shortUrl': 'https://trello.com/c/AAaaBBbb', 'url': 'https://trello.com/c/AAaBBbb/42-so-long'} CARD2 = {'id': 'kard', 'name': 'Card 2', 'members': [{'username': 'mario'}]} CARD3 = {'id': 'K4rD', 'name': 'Card 3', 'members': []} LIST1 = {'id': 'L15T', 'name': 'List 1'} LIST2 = {'id': 'ZZZZ', 'name': 'List 2'} COMMENT1 = { "type": "commentCard", "data": { "text": "Preums" }, "memberCreator": { "username": "luidgi" } } COMMENT2 = { "type": "commentCard", "data": { "text": "Deuz" }, "memberCreator": { "username": "mario" } } def setUp(self): super(TestTrelloService, self).setUp() self.config = configparser.RawConfigParser() self.config.add_section('general') self.config.add_section('mytrello') self.config.set('mytrello', 'trello.api_key', 'XXXX') self.config.set('mytrello', 'trello.token', 'YYYY') self.service_config = ServiceConfig( TrelloService.CONFIG_PREFIX, self.config, 'mytrello') responses.add(responses.GET, 'https://api.trello.com/1/lists/L15T/cards/open', json=[self.CARD1, self.CARD2, self.CARD3]) responses.add(responses.GET, 'https://api.trello.com/1/boards/B04RD/lists/open', json=[self.LIST1, self.LIST2]) responses.add(responses.GET, 'https://api.trello.com/1/boards/F00', json={'id': 'F00', 'name': 'Foo Board'}) responses.add(responses.GET, 'https://api.trello.com/1/boards/B4R', json={'id': 'B4R', 'name': 'Bar Board'}) responses.add(responses.GET, 'https://api.trello.com/1/members/me/boards', json=[self.BOARD]) responses.add(responses.GET, 'https://api.trello.com/1/cards/C4RD/actions', json=[self.COMMENT1, self.COMMENT2]) @responses.activate def test_get_boards_config(self): self.config.set('mytrello', 'trello.include_boards', 'F00, B4R') service = TrelloService(self.config, 'general', 'mytrello') boards = service.get_boards() self.assertEqual(list(boards), [{'id': 'F00', 'name': 'Foo Board'}, {'id': 'B4R', 'name': 'Bar Board'}]) @responses.activate def test_get_boards_api(self): service = TrelloService(self.config, 'general', 'mytrello') boards = service.get_boards() self.assertEqual(list(boards), [self.BOARD]) @responses.activate def test_get_lists(self): service = TrelloService(self.config, 'general', 'mytrello') lists = service.get_lists('B04RD') self.assertEqual(list(lists), [self.LIST1, self.LIST2]) @responses.activate def test_get_lists_include(self): self.config.set('mytrello', 'trello.include_lists', 'List 1') service = TrelloService(self.config, 'general', 'mytrello') lists = service.get_lists('B04RD') self.assertEqual(list(lists), [self.LIST1]) @responses.activate def test_get_lists_exclude(self): self.config.set('mytrello', 'trello.exclude_lists', 'List 1') service = TrelloService(self.config, 'general', 'mytrello') lists = service.get_lists('B04RD') self.assertEqual(list(lists), [self.LIST2]) @responses.activate def test_get_cards(self): service = TrelloService(self.config, 'general', 'mytrello') cards = service.get_cards('L15T') self.assertEqual(list(cards), [self.CARD1, self.CARD2, self.CARD3]) @responses.activate def test_get_cards_assigned(self): self.config.set('mytrello', 'trello.only_if_assigned', 'tintin') service = TrelloService(self.config, 'general', 'mytrello') cards = service.get_cards('L15T') self.assertEqual(list(cards), [self.CARD1]) @responses.activate def test_get_cards_assigned_unassigned(self): self.config.set('mytrello', 'trello.only_if_assigned', 'tintin') self.config.set('mytrello', 'trello.also_unassigned', 'true') service = TrelloService(self.config, 'general', 'mytrello') cards = service.get_cards('L15T') self.assertEqual(list(cards), [self.CARD1, self.CARD3]) @responses.activate def test_get_comments(self): service = TrelloService(self.config, 'general', 'mytrello') comments = service.get_comments('C4RD') self.assertEqual(list(comments), [self.COMMENT1, self.COMMENT2]) @responses.activate def test_annotations(self): service = TrelloService(self.config, 'general', 'mytrello') annotations = service.annotations(self.CARD1) self.assertEqual( list(annotations), ["@luidgi - Preums", "@mario - Deuz"]) @responses.activate def test_annotations_with_link(self): self.config.set('general', 'annotation_links', 'true') service = TrelloService(self.config, 'general', 'mytrello') annotations = service.annotations(self.CARD1) self.assertEqual( list(annotations), ["https://trello.com/c/AAaaBBbb", "@luidgi - Preums", "@mario - Deuz"]) @responses.activate def test_issues(self): self.config.set('mytrello', 'trello.include_lists', 'List 1') self.config.set('mytrello', 'trello.only_if_assigned', 'tintin') service = TrelloService(self.config, 'general', 'mytrello') issues = service.issues() expected = { 'description': u'(bw)#1 - Card 1 .. https://trello.com/c/AAaaBBbb', 'priority': 'M', 'project': 'My Board', 'trelloboard': 'My Board', 'trellolist': 'List 1', 'trellocard': 'Card 1', 'trellocardid': 'C4RD', 'trelloshortlink': 'abcd', 'trelloshorturl': 'https://trello.com/c/AAaaBBbb', 'trellourl': 'https://trello.com/c/AAaBBbb/42-so-long', 'annotations': [ "@luidgi - Preums", "@mario - Deuz" ], 'tags': []} actual = next(issues).get_taskwarrior_record() self.assertEqual(expected, actual) maxDiff = None @patch('bugwarrior.services.trello.die') def test_validate_config(self, die): TrelloService.validate_config(self.service_config, 'mytrello') die.assert_not_called() @patch('bugwarrior.services.trello.die') def test_valid_config_no_access_token(self, die): self.config.remove_option('mytrello', 'trello.token') TrelloService.validate_config(self.service_config, 'mytrello') die.assert_called_with("[mytrello] has no 'trello.token'") @patch('bugwarrior.services.trello.die') def test_valid_config_no_api_key(self, die): self.config.remove_option('mytrello', 'trello.api_key') TrelloService.validate_config(self.service_config, 'mytrello') die.assert_called_with("[mytrello] has no 'trello.api_key'") bugwarrior-1.5.1/tests/test_redmine.py0000644000175000017500000001010113111574702022005 0ustar threebeanthreebean00000000000000from builtins import next import datetime import mock import dateutil import responses from bugwarrior.services.redmine import RedMineService from .base import ServiceTest, AbstractServiceTest class TestRedmineIssue(AbstractServiceTest, ServiceTest): maxDiff = None SERVICE_CONFIG = { 'redmine.url': 'https://something', 'redmine.key': 'something_else', 'redmine.issue_limit': '100', } arbitrary_created = datetime.datetime.utcnow().replace( tzinfo=dateutil.tz.tz.tzutc(), microsecond=0) - datetime.timedelta(1) arbitrary_updated = datetime.datetime.utcnow().replace( tzinfo=dateutil.tz.tz.tzutc(), microsecond=0) arbitrary_issue = { "assigned_to": { "id": 35546, "name": "Adam Coddington" }, "author": { "id": 35546, "name": "Adam Coddington" }, "created_on": arbitrary_created.isoformat(), "due_on": "2016-12-30T16:40:29Z", "description": "This is a test issue.", "done_ratio": 0, "id": 363901, "priority": { "id": 4, "name": "Normal" }, "project": { "id": 27375, "name": "Boiled Cabbage - Yum" }, "status": { "id": 1, "name": "New" }, "subject": "Biscuits", "tracker": { "id": 4, "name": "Task" }, "updated_on": arbitrary_updated.isoformat(), } def setUp(self): super(TestRedmineIssue, self).setUp() self.service = self.get_mock_service(RedMineService) def test_to_taskwarrior(self): arbitrary_url = 'http://lkjlj.com' issue = self.service.get_issue_for_record(self.arbitrary_issue) expected_output = { 'annotations': [], 'project': issue.get_project_name(), 'priority': self.service.default_priority, issue.DUEDATE: None, issue.ASSIGNED_TO: self.arbitrary_issue['assigned_to']['name'], issue.AUTHOR: self.arbitrary_issue['author']['name'], issue.CATEGORY: None, issue.DESCRIPTION: self.arbitrary_issue['description'], issue.ESTIMATED_HOURS: None, issue.STATUS: 'New', issue.URL: arbitrary_url, issue.SUBJECT: self.arbitrary_issue['subject'], issue.TRACKER: u'Task', issue.CREATED_ON: self.arbitrary_created, issue.UPDATED_ON: self.arbitrary_updated, issue.ID: self.arbitrary_issue['id'], issue.SPENT_HOURS: None, issue.START_DATE: None, } def get_url(*args): return arbitrary_url with mock.patch.object(issue, 'get_issue_url', side_effect=get_url): actual_output = issue.to_taskwarrior() self.assertEqual(actual_output, expected_output) @responses.activate def test_issues(self): self.add_response( 'https://something/issues.json?limit=100', json={'issues': [self.arbitrary_issue]}) issue = next(self.service.issues()) expected = { 'annotations': [], issue.DUEDATE: None, 'description': u'(bw)Is#363901 - Biscuits .. https://something/issues/363901', 'priority': 'M', 'project': u'boiledcabbageyum', 'redmineid': 363901, issue.SPENT_HOURS: None, issue.START_DATE: None, 'redmineassignedto': 'Adam Coddington', 'redmineauthor': 'Adam Coddington', issue.CATEGORY: None, issue.DESCRIPTION: self.arbitrary_issue['description'], issue.ESTIMATED_HOURS: None, issue.STATUS: 'New', 'redminesubject': u'Biscuits', 'redminetracker': u'Task', issue.CREATED_ON: self.arbitrary_created, issue.UPDATED_ON: self.arbitrary_updated, 'redmineurl': u'https://something/issues/363901', 'tags': []} self.assertEqual(issue.get_taskwarrior_record(), expected) bugwarrior-1.5.1/tests/test_templates.py0000644000175000017500000000601213111574702022366 0ustar threebeanthreebean00000000000000from bugwarrior.services import Issue from .base import ServiceTest class TestTemplates(ServiceTest): def setUp(self): super(TestTemplates, self).setUp() self.arbitrary_default_description = 'Construct Library on Terminus' self.arbitrary_issue = { 'project': 'end_of_empire', 'priority': 'H', } def get_issue( self, templates=None, issue=None, description=None, add_tags=None ): templates = {} if templates is None else templates origin = { 'annotation_length': 100, # Arbitrary 'default_priority': 'H', # Arbitrary 'description_length': 100, # Arbitrary 'templates': templates, 'shorten': False, # Arbitrary 'add_tags': add_tags if add_tags else [], } issue = Issue({}, origin) issue.to_taskwarrior = lambda: ( self.arbitrary_issue if description is None else description ) issue.get_default_description = lambda: ( self.arbitrary_default_description if description is None else description ) return issue def test_default_taskwarrior_record(self): issue = self.get_issue({}) record = issue.get_taskwarrior_record() expected_record = self.arbitrary_issue.copy() expected_record.update({ 'description': self.arbitrary_default_description, 'tags': [], }) self.assertEqual(record, expected_record) def test_override_description(self): description_template = "{{ priority }} - {{ description }}" issue = self.get_issue({ 'description': description_template }) record = issue.get_taskwarrior_record() expected_record = self.arbitrary_issue.copy() expected_record.update({ 'description': '%s - %s' % ( self.arbitrary_issue['priority'], self.arbitrary_default_description, ), 'tags': [], }) self.assertEqual(record, expected_record) def test_override_project(self): project_template = "wat_{{ project|upper }}" issue = self.get_issue({ 'project': project_template }) record = issue.get_taskwarrior_record() expected_record = self.arbitrary_issue.copy() expected_record.update({ 'description': self.arbitrary_default_description, 'project': 'wat_%s' % self.arbitrary_issue['project'].upper(), 'tags': [], }) self.assertEqual(record, expected_record) def test_tag_templates(self): issue = self.get_issue(add_tags=['one', '{{ project }}']) record = issue.get_taskwarrior_record() expected_record = self.arbitrary_issue.copy() expected_record.update({ 'description': self.arbitrary_default_description, 'tags': ['one', self.arbitrary_issue['project']] }) self.assertEqual(record, expected_record) bugwarrior-1.5.1/tests/test_bugzilla.pyc0000644000175000017500000001426513111575063022356 0ustar threebeanthreebean00000000000000ó Âù&Yc@sÍddlmZddlmZddlZddlmZddlZddlmZddl m Z m Z m Z ddl mZd efd „ƒYZd e fd „ƒYZd e e fd„ƒYZdS(iÿÿÿÿ(tnext(tobjectN(t namedtuple(tBugzillaServicei(t ConfigTestt ServiceTesttAbstractServiceTest(t ServiceConfigtFakeBugzillaLibcBseZd„Zd„ZRS(cCs ||_dS(N(trecord(tselfR ((s7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyt__init__scCs.tdt|jjƒƒƒ}||jgS(NtRecord(RtlistR tkeys(R tqueryR ((s7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyRs(t__name__t __module__R R(((s7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyR s tTestBugzillaServiceConfigcBsYeZd„Zejdƒd„ƒZejdƒd„ƒZejdƒd„ƒZRS(cCsatt|ƒjƒtjƒ|_|jjdƒ|jjdƒttj |jdƒ|_ dS(Ntgeneraltmybz( tsuperRtsetUpt configparsertRawConfigParsertconfigt add_sectionRRt CONFIG_PREFIXtservice_config(R ((s7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyRs sbugwarrior.services.bz.diecCsc|jjdddƒ|jjdddƒ|jjdddƒtj|jdƒ|jƒdS(NRsbugzilla.base_urishttp://one.com/sbugzilla.usernametmesbugzilla.passwordtmypas(RtsetRtvalidate_configRtassert_not_called(R tdie((s7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyt&test_validate_config_username_password s cCsc|jjdddƒ|jjdddƒ|jjdddƒtj|jdƒ|jƒdS(NRsbugzilla.base_urishttp://one.com/sbugzilla.usernameRsbugzilla.api_keyt123(RRRR RR!(R R"((s7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyttest_validate_config_api_key(s cCsP|jjdddƒ|jjdddƒtj|jdƒ|jdƒdS(NRsbugzilla.base_urishttp://one.com/sbugzilla.api_keyR$s![mybz] has no 'bugzilla.username'(RRRR Rtassert_called_with(R R"((s7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyt(test_validate_config_api_key_no_username0s(RRRtmocktpatchR#R%R'(((s7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyRs tTestBugzillaServicecBs‡eZidd6dd6dd6Zidd6dd 6d d 6d d 6dd6dd6gd6Zd„Zd„Zd„Zd„Zd„ZRS(shttp://one.com/sbugzilla.base_urithellosbugzilla.usernamettheresbugzilla.passwordtProducttproductt Somethingt componentturgenttprioritytNEWtstatussThis is the issue summarytsummaryi‡ÖtidtflagscCs?tt|ƒjƒtjdƒ|jtƒ|_WdQXdS(Nsbugzilla.Bugzilla(RR*RR(R)tget_mock_serviceRtservice(R ((s7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyRIscOs1tt|ƒj||Ž}t|jƒ|_|S(N(RR*R8Rtarbitrary_recordtbz(R targstkwargsR9((s7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyR8Ns c CsGtjdƒ2|jtdidd6dd6dd6ƒ|_WdQXdS( Nsbugzilla.Bugzillatconfig_overridesshttp://one.com/sbugzilla.base_uriRsbugzilla.usernameR$sbugzilla.api_key(R(R)R8RR9(R ((s7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyttest_api_key_suppliedTscCsæidd6dgd6}|jj|j|ƒ}i |jdd6|j|jdd6|dd6|jd|j6|d|j6|jd |j6|jd |j6|jd |j6|jd|j 6}|j ƒ}|j ||ƒdS( Nshttp://path/to/issue/turltTwot annotationsR0tprojectR2R4R5R6R.( R9tget_issue_for_recordR:t PRIORITY_MAPtSTATUStURLtSUMMARYtBUG_IDtPRODUCTt COMPONENTtto_taskwarriort assertEqual(R tarbitrary_extratissuetexpected_outputt actual_output((s7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyttest_to_taskwarrior^s$     cCs‚t|jjƒƒ}i gd6dd6dd6dd6dd 6d d 6d d 6dd6dd6d d6gd6}|j|jƒ|ƒdS(NRBi‡Öt bugzillabugidR3tbugzillastatussThis is the issue summarytbugzillasummaryu/https://http://one.com//show_bug.cgi?id=1234567t bugzillaurlR-tbugzillaproductR/tbugzillacomponentu](bw)Is#1234567 - This is the issue summary .. https://http://one.com//show_bug.cgi?id=1234567t descriptiontHR2RCttags(RR9tissuesRMtget_taskwarrior_record(R ROtexpected((s7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyt test_issues{s ( RRtSERVICE_CONFIGR:RR8R?RRR_(((s7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyR*8s"     (tbuiltinsRRR(t collectionsRRtbugwarrior.services.bzRtbaseRRRtbugwarrior.configRRRR*(((s7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyts   "bugwarrior-1.5.1/tests/test_config.py0000644000175000017500000001305413111574702021641 0ustar threebeanthreebean00000000000000# coding: utf-8 from __future__ import unicode_literals import os import configparser from unittest import TestCase import bugwarrior.config as config from .base import ConfigTest class TestGetConfigPath(ConfigTest): def create(self, path): """ Create an empty file in the temporary directory, return the full path. """ fpath = os.path.join(self.tempdir, path) if not os.path.exists(os.path.dirname(fpath)): os.makedirs(os.path.dirname(fpath)) open(fpath, 'a').close() return fpath def test_default(self): """ If it exists, use the file at $XDG_CONFIG_HOME/bugwarrior/bugwarriorrc """ rc = self.create('.config/bugwarrior/bugwarriorrc') self.assertEquals(config.get_config_path(), rc) def test_legacy(self): """ Falls back on .bugwarriorrc if it exists """ rc = self.create('.bugwarriorrc') self.assertEquals(config.get_config_path(), rc) def test_xdg_first(self): """ If both files above exist, the one in $XDG_CONFIG_HOME takes precedence """ self.create('.bugwarriorrc') rc = self.create('.config/bugwarrior/bugwarriorrc') self.assertEquals(config.get_config_path(), rc) def test_no_file(self): """ If no bugwarriorrc exist anywhere, the path to the prefered one is returned. """ self.assertEquals( config.get_config_path(), os.path.join(self.tempdir, '.config/bugwarrior/bugwarriorrc')) def test_BUGWARRIORRC(self): """ If $BUGWARRIORRC is set, it takes precedence over everything else (even if the file doesn't exist). """ rc = os.path.join(self.tempdir, 'my-bugwarriorc') os.environ['BUGWARRIORRC'] = rc self.create('.bugwarriorrc') self.create('.config/bugwarrior/bugwarriorrc') self.assertEquals(config.get_config_path(), rc) def test_BUGWARRIORRC_empty(self): """ If $BUGWARRIORRC is set but emty, it is not used and the default file is used instead. """ os.environ['BUGWARRIORRC'] = '' rc = self.create('.config/bugwarrior/bugwarriorrc') self.assertEquals(config.get_config_path(), rc) class TestGetDataPath(ConfigTest): def setUp(self): super(TestGetDataPath, self).setUp() self.config = configparser.RawConfigParser() self.config.add_section('general') def assertDataPath(self, expected_datapath): self.assertEqual( expected_datapath, config.get_data_path(self.config, 'general')) def test_TASKDATA(self): """ TASKDATA should be respected, even when taskrc's data.location is set. """ datapath = os.environ['TASKDATA'] = os.path.join(self.tempdir, 'data') self.assertDataPath(datapath) def test_taskrc_datalocation(self): """ When TASKDATA is not set, data.location in taskrc should be respected. """ os.environ['TASKDATA'] = '' self.assertDataPath(self.lists_path) def test_unassigned(self): """ When data path is not assigned, use default location. """ # Empty taskrc. with open(self.taskrc, 'w'): pass os.environ['TASKDATA'] = '' self.assertDataPath(os.path.expanduser('~/.task')) class TestOracleEval(TestCase): def test_echo(self): self.assertEqual(config.oracle_eval("echo fööbÃ¥r"), "fööbÃ¥r") class TestBugwarriorConfigParser(TestCase): def setUp(self): self.config = config.BugwarriorConfigParser() self.config.add_section('general') self.config.set('general', 'someint', '4') self.config.set('general', 'somenone', '') self.config.set('general', 'somechar', 'somestring') def test_getint(self): self.assertEqual(self.config.getint('general', 'someint'), 4) def test_getint_none(self): self.assertEqual(self.config.getint('general', 'somenone'), None) def test_getint_valueerror(self): with self.assertRaises(ValueError): self.config.getint('general', 'somechar') class TestServiceConfig(TestCase): def setUp(self): self.target = 'someservice' self.config = config.BugwarriorConfigParser() self.config.add_section(self.target) self.config.set(self.target, 'someprefix.someint', '4') self.config.set(self.target, 'someprefix.somenone', '') self.config.set(self.target, 'someprefix.somechar', 'somestring') self.config.set(self.target, 'someprefix.somebool', 'true') self.service_config = config.ServiceConfig( 'someprefix', self.config, self.target) def test_configparser_proxy(self): """ Methods not defined in ServiceConfig should be proxied to configparser. """ self.assertTrue( self.service_config.has_option(self.target, 'someprefix.someint')) def test__contains__(self): self.assertTrue('someint' in self.service_config) def test_get(self): self.assertEqual(self.service_config.get('someint'), '4') def test_get_default(self): self.assertEqual( self.service_config.get('someoption', default='somedefault'), 'somedefault' ) def test_get_default_none(self): self.assertIsNone(self.service_config.get('someoption')) def test_get_to_type(self): self.assertIs( self.service_config.get('somebool', to_type=config.asbool), True ) bugwarrior-1.5.1/tests/base.pyc0000644000175000017500000001113113111575063020405 0ustar threebeanthreebean00000000000000ó Âù&Yc@sÁddlmZddlZddlZddlZddlZddlZddlZddl m Z ddl m Z defd„ƒYZ dejfd„ƒYZd efd „ƒYZdS( iÿÿÿÿ(tobjectN(tconfig(tBugwarriorDatatAbstractServiceTestcBs eZdZd„Zd„ZRS(sE Ensures that certain test methods are implemented for each service. cCs t‚dS(s Test Service.to_taskwarrior(). N(tNotImplementedError(tself((s./home/threebean/devel/bugwarrior/tests/base.pyttest_to_taskwarriorscCs t‚dS(s Test Service.issues(). - When the API is accessed via requests, use the responses library to mock requests. - When the API is accessed via a third party library, substitute a fake implementation class for it. N(R(R((s./home/threebean/devel/bugwarrior/tests/base.pyt test_issuess (t__name__t __module__t__doc__RR(((s./home/threebean/devel/bugwarrior/tests/base.pyRs t ConfigTestcBs eZdZd„Zd„ZRS(sU Creates config files, configures the environment, and cleans up afterwards. cCsÿtjjƒ|_tjddƒ|_tjj|jdƒ|_ tjj|jdƒ|_ tj |j ƒt |j dƒ}|j d|j ƒWdQX|jtjdt classmethodR0R#RIt staticmethodRP(((s./home/threebean/devel/bugwarrior/tests/base.pyR+;s !(tbuiltinsRR@R'tos.pathRRtunittestRKR Rtbugwarrior.dataRRtTestCaseR R+(((s./home/threebean/devel/bugwarrior/tests/base.pyts      bugwarrior-1.5.1/tests/test_gitlab.pyc0000644000175000017500000001717013111575063022005 0ustar threebeanthreebean00000000000000ó Âù&Yc@sÉddlmZejƒddlmZddlZddlZddlZddlZddl m Z ddl m Z ddl mZmZmZdefd „ƒYZd eefd „ƒYZdS( iÿÿÿÿ(tstandard_library(tnextN(t ServiceConfig(t GitlabServicei(t ConfigTestt ServiceTesttAbstractServiceTesttTestGitlabServicecBs5eZd„Zd„Zd„Zd„Zd„ZRS(cCstt|ƒjƒtjƒ|_|jjdƒ|jjdƒ|jjdddƒ|jjdddƒtt j |jdƒ|_ dS(Ntgeneralt myservices gitlab.logintfoobars gitlab.tokentXXXXXX( tsuperRtsetUpt configparsertRawConfigParsertconfigt add_sectiontsetRRt CONFIG_PREFIXtservice_config(tself((s5/home/threebean/devel/bugwarrior/tests/test_gitlab.pyR scCs |jtj|jƒdƒdS(Nsgitlab://foobar@gitlab.com(t assertEqualRtget_keyring_serviceR(R((s5/home/threebean/devel/bugwarrior/tests/test_gitlab.pyt%test_get_keyring_service_default_hostscCs6|jjdddƒ|jtj|jƒdƒdS(NR s gitlab.hostsgitlab.example.coms"gitlab://foobar@gitlab.example.com(RRRRRR(R((s5/home/threebean/devel/bugwarrior/tests/test_gitlab.pyt$test_get_keyring_service_custom_host!scCsH|jjdddƒt|jddƒ}|j|jddgƒdS(NR sgitlab.include_repossbaz, banana/treeRs foobar/bazs banana/tree(RRRRt include_repos(Rtservice((s5/home/threebean/devel/bugwarrior/tests/test_gitlab.pyt,test_add_default_namespace_to_included_repos'scCsH|jjdddƒt|jddƒ}|j|jddgƒdS(NR sgitlab.exclude_repossbaz, banana/treeRs foobar/bazs banana/tree(RRRRt exclude_repos(RR((s5/home/threebean/devel/bugwarrior/tests/test_gitlab.pyt,test_add_default_namespace_to_excluded_repos,s(t__name__t __module__R RRRR(((s5/home/threebean/devel/bugwarrior/tests/test_gitlab.pyRs    tTestGitlabIssuecBseZd9Zidd6dd6dd6Zejjƒejddƒjde j d d ƒZ ejjƒjde j d d ƒZ ejj ejjƒejjjƒƒjde j ƒZi d d 6d d6dd6dd6dd6dgd6idd 6dd6dd6ejƒjƒd6dd6dd6dd6d6idd 6d d!6d"d#6d$d%6d&d6d'd6d(6idd 6d)d!6d*d#6d+d%6d&d6d,d6d-6d.d6e jƒd6e jƒd6Zid/d06d1d16d2d36gd46Zd5„Zd6„Zd7„Zejd8„ƒZRS(:sgitlab.example.coms gitlab.hosttarbitrary_logins gitlab.logintarbitrary_tokens gitlab.tokenthoursittzinfot microsecondii*tiditiidit project_idsAdd user settingsttitlett descriptiontfeaturetlabelssv1.0tdue_datetclosedtstates2012-07-04T13:42:48Zt updated_att created_att milestoneit jack_smithtusernamesjack@example.comtemails Jack Smithtnametactives2012-05-23T08:01:01Ztassigneet john_smithsjohn@example.coms John Smiths2012-05-23T08:00:58Ztauthortopeneds>https://gitlab.example.com/arbitrary_username/project/issues/3t issue_urltprojecttissuettypet annotationscCs)tt|ƒjƒ|jtƒ|_dS(N(R R!R tget_mock_serviceRR(R((s5/home/threebean/devel/bugwarrior/tests/test_gitlab.pyR oscCs8|jj|j|jƒ}|j|jdƒdƒdS(Ns needs workt needs_work(Rtget_issue_for_recordtarbitrary_issuetarbitrary_extraRt_normalize_label_to_tag(RR@((s5/home/threebean/devel/bugwarrior/tests/test_gitlab.pyttest_normalize_label_to_tagss   cCsot|j_|jj|j|jƒ}i|jdd6|jjd6gd6dgd6|jd|j6d|j6|jd|j 6|jd|j 6|jd |j 6|jd |j 6|j jd d ƒ|j6|jjd d ƒ|j6|j|j6|jd |j6|jdd |j6d |j6d |j6d |j6d|j6d|j6}|jƒ}|j||ƒdS(NR?tpriorityRBufeaturettagsR>R1RAR*R(R&iR,R4R;R5(tTrueRtimport_labels_as_tagsRERFRGtdefault_prioritytURLtREPOtSTATEtTYPEtTITLEtNUMBERtarbitrary_updatedtreplacet UPDATED_ATtarbitrary_createdt CREATED_ATtarbitrary_duedatetDUEDATEt DESCRIPTIONt MILESTONEtUPVOTESt DOWNVOTEStWORK_IN_PROGRESStAUTHORtASSIGNEEtto_taskwarriorR(RR@texpected_outputt actual_output((s5/home/threebean/devel/bugwarrior/tests/test_gitlab.pyttest_to_taskwarrior{s6             cCsC|jddidd6dd6dd6gƒ|jd d|jgƒ|jd diid d 6d 6dd6gƒt|jjƒƒ}idgd6dd6dd6dd6|jd6dd6dd6dd6dd 6d!d"6d#d$6d%d&6d'd(6|jd)6|jd*6dd+6d,d-6dd.6d/d06d!d16gd26}|j|j ƒ|ƒdS(3Ns>https://gitlab.example.com/api/v3/projects?per_page=100&page=1tjsoniR'sarbitrary_username/projecttpaths example.comtweb_urlsGhttps://gitlab.example.com/api/v3/projects/1/issues?per_page=100&page=1sPhttps://gitlab.example.com/api/v3/projects/1/issues/42/notes?per_page=100&page=1R;R6R<s Some comment.tbodyu@john_smith - Some comment.RBu4(bw)Is#3 - Add user settings .. example.com/issues/3R,u jack_smithtgitlabassigneeu john_smitht gitlabauthortgitlabcreatedonutgitlabdescriptionitgitlabdownvotesuv1.0tgitlabmilestoneit gitlabnumberuarbitrary_username/projectt gitlabrepouopenedt gitlabstateuAdd user settingst gitlabtitleR@t gitlabtypetgitlabupdatedatt gitlabduedatet gitlabupvotesuexample.com/issues/3t gitlaburlt gitlabwiptMRJR?RK( t add_responseRFRRtissuesRXRURZRtget_taskwarrior_record(RR@texpected((s5/home/threebean/devel/bugwarrior/tests/test_gitlab.pyt test_issuesœsL      N(RR tNonetmaxDifftSERVICE_CONFIGtdatetimetutcnowt timedeltaRVtpytztUTCRXRUtcombinetdatettodaytminttimeRZt isoformatRFRGR RIRft responsestactivateR€(((s5/home/threebean/devel/bugwarrior/tests/test_gitlab.pyR!2sj "          !(tfutureRtinstall_aliasestbuiltinsRRR„R‡Rtbugwarrior.configRtbugwarrior.services.gitlabRtbaseRRRRR!(((s5/home/threebean/devel/bugwarrior/tests/test_gitlab.pyts     "bugwarrior-1.5.1/tests/test_string_compat.pyc0000644000175000017500000000251713111575063023413 0ustar threebeanthreebean00000000000000ó Âù&Yc@sUddlZddlZddlmZddlmZdejfd„ƒYZdS(iÿÿÿÿN(t MagicMock(tIssuetStringCompatTestcBseZdZd„ZRS(sjThis class implements method to test correct and compatible implementation of __str__ and __repr__ methodscCs°i}idd6dd6dd6gd6}t||ƒ}td|ƒ|_tddƒ|_|jt|ƒtjƒ|j|jƒtjƒ|j tj p¨t |dƒƒd S( scheck Issue classttargettpriotdefault_priorityt templatestadd_tagst return_valuet descriptiont __unicode__N( RRtto_taskwarriortget_default_descriptiontassertIsInstancetstrtsixt string_typest__repr__tassert_tPY3thasattr(tselftrecordtorigintissue((s</home/threebean/devel/bugwarrior/tests/test_string_compat.pyttest_issue_str s  (t__name__t __module__t__doc__R(((s</home/threebean/devel/bugwarrior/tests/test_string_compat.pyRs(tunittestRtmockRtbugwarrior.servicesRtTestCaseR(((s</home/threebean/devel/bugwarrior/tests/test_string_compat.pyts  bugwarrior-1.5.1/tests/test_string_compat.py0000644000175000017500000000153613111574702023247 0ustar threebeanthreebean00000000000000import unittest import six from mock import MagicMock from bugwarrior.services import Issue class StringCompatTest(unittest.TestCase): """This class implements method to test correct and compatible implementation of __str__ and __repr__ methods""" def test_issue_str(self): "check Issue class" record = {} origin = {'target': 'target', 'default_priority': 'prio', 'templates': 'templates', 'add_tags': []} issue = Issue(record, origin) issue.to_taskwarrior = MagicMock(return_value=record) issue.get_default_description = MagicMock(return_value='description') self.assertIsInstance(str(issue), six.string_types) self.assertIsInstance(issue.__repr__(), six.string_types) self.assert_(six.PY3 or hasattr(issue, '__unicode__')) bugwarrior-1.5.1/tests/test_megaplan.py0000644000175000017500000000514013111574702022155 0ustar threebeanthreebean00000000000000from builtins import next from builtins import object import mock import unittest try: from bugwarrior.services.mplan import MegaplanService except SyntaxError: raise unittest.SkipTest( 'Upstream python-megaplan does not support python3 yet.') from .base import ServiceTest, AbstractServiceTest class FakeMegaplanClient(object): def __init__(self, record): self.record = record def get_actual_tasks(self): return [self.record] class TestMegaplanIssue(AbstractServiceTest, ServiceTest): SERVICE_CONFIG = { 'megaplan.hostname': 'something', 'megaplan.login': 'something_else', 'megaplan.password': 'aljlkj', } name_parts = ['one', 'two', 'three'] arbitrary_issue = { 'Id': 10, 'Name': '|'.join(name_parts) } def setUp(self): super(TestMegaplanIssue, self).setUp() with mock.patch('megaplan.Client'): self.service = self.get_mock_service(MegaplanService) def get_mock_service(self, *args, **kwargs): service = super(TestMegaplanIssue, self).get_mock_service( *args, **kwargs) service.client = FakeMegaplanClient(self.arbitrary_issue) return service def test_to_taskwarrior(self): arbitrary_project = 'one' arbitrary_url = 'http://one.com/' issue = self.service.get_issue_for_record(self.arbitrary_issue) expected_output = { 'project': arbitrary_project, 'priority': self.service.default_priority, issue.FOREIGN_ID: self.arbitrary_issue['Id'], issue.URL: arbitrary_url, issue.TITLE: self.name_parts[-1] } def get_url(*args): return arbitrary_url def get_project(*args): return arbitrary_project with mock.patch.multiple( issue, get_project=mock.DEFAULT, get_issue_url=mock.DEFAULT ) as mocked: mocked['get_project'].side_effect = get_project mocked['get_issue_url'].side_effect = get_url actual_output = issue.to_taskwarrior() self.assertEqual(actual_output, expected_output) def test_issues(self): issue = next(self.service.issues()) expected = { 'description': '(bw)Is#10 - three .. https://something/task/10/card/', 'megaplanid': 10, 'megaplantitle': 'three', 'megaplanurl': 'https://something/task/10/card/', 'priority': 'M', 'project': 'something', 'tags': []} self.assertEqual(issue.get_taskwarrior_record(), expected) bugwarrior-1.5.1/tests/__pycache__/0000755000175000017500000000000013111575230021165 5ustar threebeanthreebean00000000000000bugwarrior-1.5.1/tests/__pycache__/test_db.cpython-34.pyc0000644000175000017500000001172513011230564025240 0ustar threebeanthreebean00000000000000î i1%X!ã@sddlZddlZddlZddlmZddlmZGdd„dej ƒZ Gdd„deƒZ Gd d „d eƒZ dS) éN)Údbé)Ú ConfigTestc@sdeZdZdd„Zdd„Zdd„Zdd„Zd d „Zd d „Zd d„Z dS)Ú TestMergeLeftcCsidgd6|_dS)NZtestingÚ annotations)Ú issue_dict)Úself©r ú1/home/threebean/devel/bugwarrior/tests/test_db.pyÚsetUp szTestMergeLeft.setUpcKs*tjd||||j||ƒdS)Nr)rÚ merge_leftÚ assertEqual)rÚlocalÚremoteÚkwargsr r r Ú assertMergedszTestMergeLeft.assertMergedcCs|ji|jƒdS)N)rr)rr r r Útest_with_dictszTestMergeLeft.test_with_dictcCs#|jtjjiƒ|jƒdS)N)rÚtaskwÚtaskÚTaskr)rr r r Útest_with_taskwszTestMergeLeft.test_with_taskwcCs|j|j|jƒdS)N)rr)rr r r Útest_already_in_syncsz"TestMergeLeft.test_already_in_synccCsMidgd6}tjd|j|ddƒ|jt|jdƒdƒdS)z7 When hamming=False, rough equivalents are duplicated. z testing rÚhammingFéN)rr rr Úlen)rrr r r Ú!test_rough_equality_hamming_falsesz/TestMergeLeft.test_rough_equality_hamming_falsecCsMidgd6}tjd|j|ddƒ|jt|jdƒdƒdS)z: When hamming=True, rough equivalents are not duplicated. z testing rrTrN)rr rr r)rrr r r Ú test_rough_equality_hamming_true"sz.TestMergeLeft.test_rough_equality_hamming_trueN) Ú__name__Ú __module__Ú __qualname__r rrrrrrr r r r r s       rc@seZdZdd„ZdS)ÚTestSynchronizecCspdd„}tjƒ}|jdƒ|jdddƒ|jdƒ|jdddƒtj|jƒ}|j|jƒigd6gd 6ƒid d 6d d 6dd6dd6}xˆt dƒD]z}t j t |fƒ|dƒ|j||ƒigd6idd6d d6d d 6dd6d d 6dd6dd6gd 6ƒq½Wd|d .get_tasksÚgeneralÚtargetsÚ my_serviceÚserviceÚgithubÚ completedr!zBlah blah blah.Ú descriptionÚissueZ githubtypezhttps://example.comZ githuburlÚMÚpriorityrÚstatusrÚidg333333@ÚurgencyzYada yada yada.rr"r#Úendr$) Ú ConfigParserÚRawConfigParserÚ add_sectionÚsetrÚ TaskWarriorÚtaskrcr r%ÚrangerÚ synchronizeÚiter)rr(Úconfigr&r0Ú_r'r r r Útest_synchronize,sj   $    z TestSynchronize.test_synchronizeN)rrrrBr r r r r *s r c@seZdZdd„ZdS)ÚTestUDAscCsºtjƒ}|jdƒ|jdddƒ|jdƒ|jdddƒtttj|dƒƒƒ}|j|dddd d d d d ddddddddddddgƒdS)Nr)r*r+r,r-z uda.githubbody.label=Github Bodyzuda.githubbody.type=stringz(uda.githubcreatedon.label=Github Createdzuda.githubcreatedon.type=datez*uda.githubmilestone.label=Github Milestonezuda.githubmilestone.type=stringz(uda.githubnumber.label=Github Issue/PR #zuda.githubnumber.type=numericz%uda.githubrepo.label=Github Repo Slugzuda.githubrepo.type=stringz"uda.githubtitle.label=Github Titlezuda.githubtitle.type=stringz uda.githubtype.label=Github Typezuda.githubtype.type=stringz(uda.githubupdatedat.label=Github Updatedzuda.githubupdatedat.type=datezuda.githuburl.label=Github URLzuda.githuburl.type=stringz uda.githubuser.label=Github Userzuda.githubuser.type=string) r7r8r9r:ÚsortedÚlistrÚget_defined_udas_as_stringsr )rr@Úudasr r r Ú test_udass6    zTestUDAs.test_udasN)rrrrHr r r r rC€s rC) ÚunittestÚ configparserr7Ú taskw.taskrÚ bugwarriorrÚbaserÚTestCaserr rCr r r r Ús    Vbugwarrior-1.5.1/tests/__pycache__/test_trello.cpython-35.pyc0000664000175000017500000002274013064355425026172 0ustar threebeanthreebean00000000000000 ÑÚÑXn#ã@sÔddlmZmZddlmZejƒddlmZddlZddl m Z ddl Z ddl m Z ddlmZmZdd lmZmZGd d „d eƒZGd d „d eƒZdS)é)Úunicode_literalsÚprint_function)Ústandard_library)ÚnextN)Úpatch)Ú ServiceConfig)Ú TrelloServiceÚ TrelloIssueé)Ú ConfigTestÚ ServiceTestcs|eZdZddddddddd d d d d ddiddigiZ‡fdd†Zdd„Zdd„Z‡S)ÚTestTrelloIssueÚidZ542bbb6583d705eb05bbe491ÚidShorté*Únamez%So long, and thanks for all the fish!Ú shortLinkZAAaaBBbbÚshortUrlzhttps://trello.com/c/AAaaBBbbÚurlz'https://trello.com/c/AAaBBbb/42-so-longÚlabelsÚfooÚbarcs\tt|ƒjƒtddddddƒ}dddd i}t|j||ƒ|_dS) NÚ inline_linksTÚdescription_lengthéÚimport_labels_as_tagsZ boardnamezHyperspatial express routeZlistnameÚ Something)Úsuperr ÚsetUpÚdictr ÚJSONÚissue)ÚselfÚoriginÚextra)Ú __class__©ú5/home/threebean/devel/bugwarrior/tests/test_trello.pyrs   zTestTrelloIssue.setUpcCs#d}|j||jjƒƒdS)z Test the generated description zJ(bw)#42 - So long, and thanks for all the .. https://trello.com/c/AAaaBBbbN)Ú assertEqualr!Úget_default_description)r"Z expected_descr&r&r'Útest_default_description"sz(TestTrelloIssue.test_default_descriptioncCs/d}|j||jjƒjddƒƒdS)z+ By default, the project is the board name zHyperspatial express routeÚprojectN)r(r!Úto_taskwarriorÚget)r"Zexpected_projectr&r&r'Útest_to_taskwarrior__project(s z,TestTrelloIssue.test_to_taskwarrior__project)Ú__name__Ú __module__Ú __qualname__r rr*r.r&r&)r%r'r s  r cs\eZdZddddiZddddddd igd d d d ddddiZdddddddigiZdddddgiZddddiZddddiZdddddid dd!iiZ ddddd"id ddiiZ ‡fd#d$†Z e j d%d&„ƒZe j d'd(„ƒZe j d)d*„ƒZe j d+d,„ƒZe j d-d.„ƒZe j d/d0„ƒZe j d1d2„ƒZe j d3d4„ƒZe j d5d6„ƒZe j d7d8„ƒZe j d9d:„ƒZe j d;d<„ƒZd=Zed>ƒd?d@„ƒZed>ƒdAdB„ƒZed>ƒdCdD„ƒZ‡S)EÚTestTrelloServicerÚB04RDrzMy BoardÚC4RDzCard 1ÚmembersÚusernameÚtintinrr rÚabcdrzhttps://trello.com/c/AAaaBBbbrz'https://trello.com/c/AAaBBbb/42-so-longZkardzCard 2ZmarioZK4rDzCard 3ÚL15TzList 1ZZZZZzList 2ÚtypeZ commentCardÚdataÚtextZPreumsZ memberCreatorZluidgiZDeuzcsktt|ƒjƒtjƒ|_|jjdƒ|jjdƒ|jjdddƒ|jjdddƒtt j |jdƒ|_ t j t jdd|j|j|jgƒt j t jd d|j|jgƒt j t jd dd d d diƒt j t jddd dd diƒt j t jdd|jgƒt j t jdd|j|jgƒdS)NÚgeneralÚmytrelloztrello.api_keyZXXXXz trello.tokenZYYYYz.https://api.trello.com/1/lists/L15T/cards/openÚjsonz0https://api.trello.com/1/boards/B04RD/lists/openz#https://api.trello.com/1/boards/F00rÚF00rz Foo Boardz#https://api.trello.com/1/boards/B4RÚB4Rz Bar Boardz*https://api.trello.com/1/members/me/boardsz+https://api.trello.com/1/cards/C4RD/actions)rr2rÚ configparserÚRawConfigParserÚconfigÚ add_sectionÚsetrrÚ CONFIG_PREFIXÚservice_configÚ responsesÚaddÚGETÚCARD1ÚCARD2ÚCARD3ÚLIST1ÚLIST2ÚBOARDÚCOMMENT1ÚCOMMENT2)r")r%r&r'rAs4       zTestTrelloService.setUpcCso|jjdddƒt|jddƒ}|jƒ}|jt|ƒddddidd dd igƒdS) Nr>ztrello.include_boardszF00, B4Rr=rr@rz Foo BoardrAz Bar Board)rDrFrÚ get_boardsr(Úlist)r"ÚserviceÚboardsr&r&r'Útest_get_boards_config]s  z(TestTrelloService.test_get_boards_configcCsAt|jddƒ}|jƒ}|jt|ƒ|jgƒdS)Nr=r>)rrDrTr(rUrQ)r"rVrWr&r&r'Útest_get_boards_apies z%TestTrelloService.test_get_boards_apicCsJt|jddƒ}|jdƒ}|jt|ƒ|j|jgƒdS)Nr=r>r3)rrDÚ get_listsr(rUrOrP)r"rVÚlistsr&r&r'Útest_get_listsksz TestTrelloService.test_get_listscCsZ|jjdddƒt|jddƒ}|jdƒ}|jt|ƒ|jgƒdS)Nr>ztrello.include_listszList 1r=r3)rDrFrrZr(rUrO)r"rVr[r&r&r'Útest_get_lists_includeqsz(TestTrelloService.test_get_lists_includecCsZ|jjdddƒt|jddƒ}|jdƒ}|jt|ƒ|jgƒdS)Nr>ztrello.exclude_listszList 1r=r3)rDrFrrZr(rUrP)r"rVr[r&r&r'Útest_get_lists_excludexsz(TestTrelloService.test_get_lists_excludecCsPt|jddƒ}|jdƒ}|jt|ƒ|j|j|jgƒdS)Nr=r>r9)rrDÚ get_cardsr(rUrLrMrN)r"rVÚcardsr&r&r'Útest_get_cardssz TestTrelloService.test_get_cardscCsZ|jjdddƒt|jddƒ}|jdƒ}|jt|ƒ|jgƒdS)Nr>ztrello.only_if_assignedr7r=r9)rDrFrr_r(rUrL)r"rVr`r&r&r'Útest_get_cards_assigned…sz)TestTrelloService.test_get_cards_assignedcCsv|jjdddƒ|jjdddƒt|jddƒ}|jdƒ}|jt|ƒ|j|jgƒdS)Nr>ztrello.only_if_assignedr7ztrello.also_unassignedÚtruer=r9)rDrFrr_r(rUrLrN)r"rVr`r&r&r'Ú"test_get_cards_assigned_unassignedŒs z4TestTrelloService.test_get_cards_assigned_unassignedcCsJt|jddƒ}|jdƒ}|jt|ƒ|j|jgƒdS)Nr=r>r4)rrDÚ get_commentsr(rUrRrS)r"rVÚcommentsr&r&r'Útest_get_comments”sz#TestTrelloService.test_get_commentscCsGt|jddƒ}|j|jƒ}|jt|ƒddgƒdS)Nr=r>z@luidgi - Preumsz @mario - Deuz)rrDÚ annotationsrLr(rU)r"rVrhr&r&r'Útest_annotationsšsz"TestTrelloService.test_annotationscCs`|jjdddƒt|jddƒ}|j|jƒ}|jt|ƒdddgƒdS)Nr=Úannotation_linksrcr>zhttps://trello.com/c/AAaaBBbbz@luidgi - Preumsz @mario - Deuz)rDrFrrhrLr(rU)r"rVrhr&r&r'Útest_annotations_with_link¡s z,TestTrelloService.test_annotations_with_linkcCsÇ|jjdddƒ|jjdddƒt|jddƒ}|jƒ}ddd d d d d d dddddddddddddddgdgi }t|ƒjƒ}|j||ƒdS)Nr>ztrello.include_listszList 1ztrello.only_if_assignedr7r=Ú descriptionz0(bw)#1 - Card 1 .. https://trello.com/c/AAaaBBbbÚpriorityÚMr+zMy BoardZ trelloboardZ trellolistZ trellocardzCard 1Z trellocardidr4Ztrelloshortlinkr8Ztrelloshorturlzhttps://trello.com/c/AAaaBBbbZ trellourlz'https://trello.com/c/AAaBBbb/42-so-longrhz@luidgi - Preumsz @mario - DeuzÚtags)rDrFrÚissuesrÚget_taskwarrior_recordr()r"rVrpÚexpectedÚactualr&r&r'Ú test_issues¬s(  zTestTrelloService.test_issuesNzbugwarrior.services.trello.diecCs!tj|jdƒ|jƒdS)Nr>)rÚvalidate_configrHÚassert_not_called)r"Údier&r&r'Útest_validate_configÆsz&TestTrelloService.test_validate_configcCs7|jjddƒtj|jdƒ|jdƒdS)Nr>z trello.tokenz [mytrello] has no 'trello.token')rDÚ remove_optionrrurHÚassert_called_with)r"rwr&r&r'Ú!test_valid_config_no_access_tokenËsz3TestTrelloService.test_valid_config_no_access_tokencCs7|jjddƒtj|jdƒ|jdƒdS)Nr>ztrello.api_keyz"[mytrello] has no 'trello.api_key')rDryrrurHrz)r"rwr&r&r'Útest_valid_config_no_api_keyÑsz.TestTrelloService.test_valid_config_no_api_key)r/r0r1rQrLrMrNrOrPrRrSrrIÚactivaterXrYr\r]r^rarbrdrgrirkrtÚmaxDiffrrxr{r|r&r&)r%r'r2/sB  !   r2)Ú __future__rrÚfuturerÚinstall_aliasesÚbuiltinsrrBÚmockrrIÚbugwarrior.configrZbugwarrior.services.trellorr Úbaser r r r2r&r&r&r'Ús    bugwarrior-1.5.1/tests/__pycache__/test_megaplan.cpython-35.pyc0000644000175000017500000000652513050354041026442 0ustar threebeanthreebean00000000000000 ´@#X` ã @s¹ddlmZddlmZddlZddlZyddlmZWn!ek roejdƒ‚YnXddl m Z m Z Gdd „d eƒZ Gd d „d e e ƒZ dS) é)Únext)ÚobjectN)ÚMegaplanServicez6Upstream python-megaplan does not support python3 yet.é)Ú ServiceTestÚAbstractServiceTestc@s(eZdZdd„Zdd„ZdS)ÚFakeMegaplanClientcCs ||_dS)N)Úrecord)Úselfr ©r ú7/home/threebean/devel/bugwarrior/tests/test_megaplan.pyÚ__init__szFakeMegaplanClient.__init__cCs |jgS)N)r )r r r r Úget_actual_taskssz#FakeMegaplanClient.get_actual_tasksN)Ú__name__Ú __module__Ú __qualname__r rr r r r rs  rcsŽeZdZddddddiZddd gZd d d d jeƒiZ‡fdd†Z‡fdd†Zdd„Z dd„Z ‡S)ÚTestMegaplanIssuezmegaplan.hostnameÚ somethingzmegaplan.loginZsomething_elsezmegaplan.passwordZaljlkjÚoneÚtwoÚthreeÚIdé ÚNameú|c s@tt|ƒjƒtjdƒ|jtƒ|_WdQRXdS)Nzmegaplan.Client)ÚsuperrÚsetUpÚmockÚpatchÚget_mock_servicerÚservice)r )Ú __class__r r r"szTestMegaplanIssue.setUpcs1tt|ƒj||Ž}t|jƒ|_|S)N)rrrrÚarbitrary_issueÚclient)r ÚargsÚkwargsr )r!r r r's z"TestMegaplanIssue.get_mock_servicec sîd‰d‰|jj|jƒ}dˆd|jj|j|jd|jˆ|j|jd i}‡fdd†}‡fd d †}tj j |d tj d tj ƒ-}||d _ ||d _ |j ƒ}WdQRX|j||ƒdS)Nrzhttp://one.com/ÚprojectÚpriorityrrcsˆS)Nr )r$)Ú arbitrary_urlr r Úget_url<sz6TestMegaplanIssue.test_to_taskwarrior..get_urlcsˆS)Nr )r$)Úarbitrary_projectr r Ú get_project?sz:TestMegaplanIssue.test_to_taskwarrior..get_projectr+Z get_issue_urléÿÿÿÿ)r Úget_issue_for_recordr"Údefault_priorityÚ FOREIGN_IDÚURLÚTITLEÚ name_partsrrÚmultipleÚDEFAULTÚ side_effectÚto_taskwarriorÚ assertEqual)r ÚissueÚexpected_outputr)r+ZmockedÚ actual_outputr )r*r(r Útest_to_taskwarrior-s"     z%TestMegaplanIssue.test_to_taskwarriorcCs_t|jjƒƒ}ddddddddd d d d d gi}|j|jƒ|ƒdS)NÚ descriptionz4(bw)Is#10 - three .. https://something/task/10/card/Z megaplanidrZ megaplantitlerZ megaplanurlzhttps://something/task/10/card/r'ÚMr&rÚtags)rr Úissuesr7Úget_taskwarrior_record)r r8Úexpectedr r r Ú test_issuesKs zTestMegaplanIssue.test_issues) rrrÚSERVICE_CONFIGr2Újoinr"rrr;rBr r )r!r rs   r)ÚbuiltinsrrrÚunittestZbugwarrior.services.mplanrÚ SyntaxErrorÚSkipTestÚbaserrrrr r r r Ús   bugwarrior-1.5.1/tests/__pycache__/test_teamlab.cpython-34.pyc0000644000175000017500000000517313010652175026265 0ustar threebeanthreebean00000000000000î ´@#X1 ã@skddlmZddlZddlZddlmZddlmZmZGdd„deeƒZ dS)é)ÚnextN)ÚTeamLabServiceé)Ú ServiceTestÚAbstractServiceTestcsŽeZdZidd6dd6dd6dd6Zid d 6d d 6id d 6d6dd6Z‡fdd†Zdd„Zejdd„ƒZ ‡S)ÚTestTeamlabIssueÚ somethingzteamlab.hostnameZalkjdsfz teamlab.loginZlkjkljzteamlab.passwordÚabcdefzteamlab.project_nameÚHelloÚtitleé ÚidéŒÚ projectOwnerrÚstatusc s?tt|ƒjƒtjdƒ|jtƒ|_WdQXdS)Nz6bugwarrior.services.teamlab.TeamLabClient.authenticate)ÚsuperrÚsetUpÚmockÚpatchÚget_mock_servicerÚservice)Úself)Ú __class__©ú6/home/threebean/devel/bugwarrior/tests/test_teamlab.pyrs zTestTeamlabIssue.setUpc sÑd‰|jj|jƒ}i|jdd6|jjd6|jd|j6|jd|j6ˆ|j6|jdd|j6}‡fdd †}t j j |d d |ƒ|j ƒ}WdQX|j ||ƒdS) Nzhttp://galkjsdflkj.com/zteamlab.project_nameÚprojectÚpriorityr r rcsˆS)Nr)Úargs)Ú arbitrary_urlrrÚget_url/sz5TestTeamlabIssue.test_to_taskwarrior..get_urlÚ get_issue_urlÚ side_effect)rÚget_issue_for_recordÚarbitrary_issueÚSERVICE_CONFIGÚdefault_priorityÚTITLEÚ FOREIGN_IDÚURLZPROJECTOWNER_IDrrÚobjectÚto_taskwarriorÚ assertEqual)rÚissueÚexpected_outputrÚ actual_outputr)rrÚtest_to_taskwarrior!s  z$TestTeamlabIssue.test_to_taskwarriorcCs†|jdd|jgƒt|jjƒƒ}idd6dd6dd6gd 6d d 6d d 6dd6dd6}|j|jƒ|ƒdS)Nz0http://something/api/1.0/project/task/@self.jsonÚjsonzR(bw)Is#10 - Hello .. http://something/products/projects/tasks.aspx?prjID=140&id=10Ú descriptionÚMrr rÚtagsr Z teamlabidrZteamlabprojectowneridr Z teamlabtitlez=http://something/products/projects/tasks.aspx?prjID=140&id=10Z teamlaburl)Ú add_responser#rrÚissuesr+Úget_taskwarrior_record)rr,ÚexpectedrrrÚ test_issues7s  zTestTeamlabIssue.test_issues) Ú__name__Ú __module__Ú __qualname__r$r#rr/Ú responsesÚactivater8rr)rrr s     r) Úbuiltinsrrr<Zbugwarrior.services.teamlabrÚbaserrrrrrrÚs   bugwarrior-1.5.1/tests/__pycache__/test_github.cpython-34.pyc0000644000175000017500000001017713010652175026142 0ustar threebeanthreebean00000000000000î ¶A#Xbã@swddlmZddlZddlZddlZddlmZddlmZm Z Gdd„de eƒZ dS)é)ÚnextN)Ú GithubServiceé)Ú ServiceTestÚAbstractServiceTestcsZeZdZdZidd6dd6dd6Zejjƒejdd ƒjd e j d d ƒZ ejjƒjd e j d d ƒZ i d d6dd6dd6dd6dd6idd6d6idd6d6idd6gd6e j ƒd6e j ƒd6d d!6Zid"d#6d$d%6gd&6Z‡fd'd(†Zd)d*„Zd+d,„Zejd-d.„ƒZ‡S)/ÚTestGithubIssueNÚarbitrary_loginz github.loginZarbitrary_passwordzgithub.passwordÚarbitrary_usernamezgithub.usernameÚhoursrÚtzinfoÚ microsecondrÚHalloÚtitlezhttp://whanot.com/Úhtml_urlzGhttps://api.github.com/repos/arbitrary_username/arbitrary_repo/issues/1Úurlé ÚnumberÚ SomethingÚbodyÚloginÚuserÚalphaÚ milestoneÚbugfixÚnameÚlabelsZ created_atZ updated_atzralphbean/bugwarriorÚrepoZoneÚprojectÚissueÚtypeÚ annotationscs)tt|ƒjƒ|jtƒ|_dS)N)ÚsuperrÚsetUpÚget_mock_servicerÚservice)Úself)Ú __class__©ú5/home/threebean/devel/bugwarrior/tests/test_github.pyr"+szTestGithubIssue.setUpcCs8|jj|j|jƒ}|j|jdƒdƒdS)Nz needs workZ needs_work)r$Úget_issue_for_recordÚarbitrary_issueÚarbitrary_extraÚ assertEqualZ_normalize_label_to_tag)r%rr'r'r(Útest_normalize_label_to_tag/s   z+TestGithubIssue.test_normalize_label_to_tagcCs#d|j_|jj|j|jƒ}i|jdd6|jjd6gd6dgd6|jd|j6|jd|j6|jd |j6|jd |j 6|jd |j 6|j |j 6|j |j6|jd |j6|jd d |j6|jdd|j6}|jƒ}|j||ƒdS)NTrÚpriorityr rÚtagsrrrrrrrrr)r$Zimport_labels_as_tagsr)r*r+Údefault_priorityÚURLZREPOÚTYPEÚTITLEÚNUMBERÚarbitrary_updatedZ UPDATED_ATÚarbitrary_createdZ CREATED_ATÚBODYÚ MILESTONEÚUSERÚto_taskwarriorr,)r%rÚexpected_outputÚ actual_outputr'r'r(Útest_to_taskwarrior7s*        z#TestGithubIssue.test_to_taskwarriorcCsZ|jddidd6idd6d6gƒ|jddid d6id d6d6gƒ|jd d|jgƒ|jd d|jgƒ|jd diidd6d6dd6gƒt|jjƒƒ}idgd6dd6dd6|jd6dd6dd6dd6dd 6d!d"6|jd#6d$d%6dd&6d'd(6d d)6gd*6}|j|jƒ|ƒdS)+Nz.https://api.github.com/user/repos?per_page=100ÚjsonZ some_reporZ some_usernamerÚownerzBhttps://api.github.com/users/arbitrary_username/repos?per_page=100Zarbitrary_repor zRhttps://api.github.com/repos/arbitrary_username/arbitrary_repo/issues?per_page=100z/https://api.github.com/user/issues?per_page=100z^https://api.github.com/repos/arbitrary_username/arbitrary_repo/issues/10/comments?per_page=100rrzArbitrary comment.rz%@arbitrary_login - Arbitrary comment.r z'(bw)Is#10 - Hallo .. http://whanot.com/Ú descriptionrZ githubbodyZgithubcreatedonrZgithubmilestonerZ githubnumberz!arbitrary_username/arbitrary_repoZ githubrepor Z githubtitlerÚ githubtypeZgithubupdatedatzhttp://whanot.com/Ú githuburlZ githubuserÚMr.rr/) Ú add_responser*rr$Úissuesr6r5r,Úget_taskwarrior_record)r%rÚexpectedr'r'r(Ú test_issuesRsN      zTestGithubIssue.test_issues)Ú__name__Ú __module__Ú __qualname__ÚmaxDiffÚSERVICE_CONFIGÚdatetimeÚutcnowÚ timedeltaÚreplaceÚpytzÚUTCr6r5Ú isoformatr*r+r"r-r=Ú responsesÚactivaterHr'r')r&r(r s:  "      r) ÚbuiltinsrrNrRrUZbugwarrior.services.githubrÚbaserrrr'r'r'r(Ús    bugwarrior-1.5.1/tests/__pycache__/test_bts.cpython-35.pyc0000664000175000017500000000614513064345432025457 0ustar threebeanthreebean00000000000000 ËÑX˜ ã@s«ddlmZddlmZddlmZddlZddlmZddlmZm Z Gdd „d eƒZ Gd d „d eƒZ Gd d „d e eƒZ dS)é)Únext)Ústr)ÚobjectN)Úbtsé)Ú ServiceTestÚAbstractServiceTestc@s:eZdZdZdZdZdZdZdZdZ dS)Ú FakeBTSBugi…^ ÚwnppziITP: bugwarrior -- Pull tickets from github, bitbucket, bugzilla, jira, trac, and others into taskwarriorZwishlistÚÚpendingN) Ú__name__Ú __module__Ú __qualname__Úbug_numÚpackageÚsubjectÚseverityÚsourceÚ forwardedr ©rrú2/home/threebean/devel/bugwarrior/tests/test_bts.pyr s r c@s(eZdZdd„Zdd„ZdS)Ú FakeBTSLibcOsdgS)Ni…^ r)ÚselfÚargsÚkwargsrrrÚget_bugsszFakeBTSLib.get_bugscCs|dgkrtgSdS)Ni…^ )r )rrrrrÚ get_statusszFakeBTSLib.get_statusN)r rrrrrrrrrs  rcsReZdZdZddddiZ‡fdd†Zdd „Zd d „Z‡S) ÚTestBTSServiceNz bts.emailzirl@debian.orgz bts.packagesÚ bugwarriorcs,tt|ƒjƒ|jtjƒ|_dS)N)ÚsuperrÚsetUpÚget_mock_servicerZ BTSServiceÚservice)r)Ú __class__rrr!)szTestBTSService.setUpcCs²|jj|jjtƒƒ}d|jtj|jdttjƒ|j tj |j tj|j tj |jtj|jtj|jtji}|jƒ}|j||ƒdS)NÚpriorityzhttps://bugs.debian.org/)r#Úget_issue_for_recordZ_record_for_bugr Ú PRIORITY_MAPrÚURLrrZSUBJECTrÚNUMBERÚPACKAGErZSOURCErZ FORWARDEDrZSTATUSr Úto_taskwarriorÚ assertEqual)rÚissueÚexpected_outputÚ actual_outputrrrÚtest_to_taskwarrior-s       z"TestBTSService.test_to_taskwarriorcCsŽtjdtƒƒt|jjƒƒ}WdQRXdddddddd d d d dd ddddddgi }|j|jƒ|ƒdS)Nz!bugwarrior.services.bts.debianbtsZ btsnumberi…^ Z btsforwardedr Z btspackager Z btssubjectziITP: bugwarrior -- Pull tickets from github, bitbucket, bugzilla, jira, trac, and others into taskwarriorZbtsurlzhttps://bugs.debian.org/810629Z btssourceÚ descriptionz–(bw)Is#810629 - ITP: bugwarrior -- Pull tickets from github, bitbucket, bugzilla, jira, trac, and others into taskwa .. https://bugs.debian.org/810629r%ÚLZ btsstatusr Útags)ÚmockÚpatchrrr#Úissuesr,Úget_taskwarrior_record)rr-ÚexpectedrrrÚ test_issuesAs zTestBTSService.test_issues)r rrÚmaxDiffÚSERVICE_CONFIGr!r0r9rr)r$rr s   r) Úbuiltinsrrrr4Úbugwarrior.servicesrÚbaserrr rrrrrrÚs   bugwarrior-1.5.1/tests/__pycache__/test_trac.cpython-34.pyc0000644000175000017500000000673113010652175025612 0ustar threebeanthreebean00000000000000î ´@#Xû ã@s¥ddlmZddlmZddlmZddlmZmZGdd„deƒZGdd „d eƒZ Gd d „d eƒZ Gd d „d eeƒZ dS)é)Únext)Úobject)Ú TracServiceé)Ú ServiceTestÚAbstractServiceTestc@s"eZdZedd„ƒZdS)ÚFakeTracTicketcCsgS)N©)Z issuenumberr r ú3/home/threebean/devel/bugwarrior/tests/test_trac.pyÚ changeLog szFakeTracTicket.changeLogN)Ú__name__Ú __module__Ú __qualname__Ú staticmethodr r r r r rs rc@seZdZeƒZdS)ÚFakeTracServerN)r r rrÚticketr r r r rs rc@sCeZdZeƒZdd„Zedd„ƒZdd„ZdS)Ú FakeTracLibcCs ||_dS)N)Úrecord)Úselfrr r r Ú__init__szFakeTracLib.__init__cCsdgS)NÚ somethingr )Úqueryr r r Ú query_ticketsszFakeTracLib.query_ticketscCsddd|jfS)Nr)r)rrr r r Ú get_ticketszFakeTracLib.get_ticketN) r r rrÚserverrrrrr r r r rs   rcseZdZidd6dd6dd6Zidd6d d 6d d 6d d6dd6Z‡fdd†Z‡fdd†Zdd„Zdd„Z‡S)Ú TestTracIssuezhttp://ljlkajsdfl.comz trac.base_urirz trac.usernameZsomepwdz trac.passwordzhttp://some/url.com/Úurlz Some SummaryÚsummaryéÌÚnumberÚcriticalÚpriorityÚ testcomponentÚ componentcs)tt|ƒjƒ|jtƒ|_dS)N)ÚsuperrÚsetUpÚget_mock_servicerÚservice)r)Ú __class__r r r%.szTestTracIssue.setUpcs1tt|ƒj||Ž}t|jƒ|_|S)N)r$rr&rÚarbitrary_issueZtrac)rÚargsÚkwargsr')r(r r r&2szTestTracIssue.get_mock_servicecCsÇiddgd6dd6}|jj|j|ƒ}i|dd6|j|jdd6|dd6|jd|j6|jd|j6|jd |j6|jd |j6}|jƒ}|j ||ƒdS) NÚalphaÚbetaÚ annotationsz some projectÚprojectr!rrrr#) r'Úget_issue_for_recordr)Ú PRIORITY_MAPÚURLÚSUMMARYÚNUMBERZ COMPONENTÚto_taskwarriorÚ assertEquals)rÚarbitrary_extraÚissueÚexpected_outputÚ actual_outputr r r Útest_to_taskwarrior7s"       z!TestTracIssue.test_to_taskwarriorcCstt|jjƒƒ}i gd6dd6dd6dd6gd6d d 6d d 6d d6dd6}|j|jƒ|ƒdS)Nr.zA(bw)Is#1 - Some Summary .. https://http://ljlkajsdfl.com/ticket/1Ú descriptionÚHr!Ú unspecifiedr/ÚtagsrZ tracnumberz Some SummaryZ tracsummaryz&https://http://ljlkajsdfl.com/ticket/1Ztracurlr"Z traccomponent)rr'ÚissuesÚ assertEqualÚget_taskwarrior_record)rr8Úexpectedr r r Ú test_issuesRs zTestTracIssue.test_issues) r r rÚSERVICE_CONFIGr)r%r&r;rDr r )r(r r s    rN) ÚbuiltinsrrZbugwarrior.services.tracrÚbaserrrrrrr r r r Úsbugwarrior-1.5.1/tests/__pycache__/test_config.cpython-34.pyc0000644000175000017500000001115713010652175026124 0ustar threebeanthreebean00000000000000î ´@#XG ã@swddlmZddlZddlZddljZddlmZGdd„deƒZGdd„deƒZ dS) é)Úunicode_literalsNé)Ú ConfigTestc@sdeZdZdd„Zdd„Zdd„Zdd„Zd d „Zd d „Zd d„Z dS)ÚTestGetConfigPathcCsitjj|j|ƒ}tjjtjj|ƒƒsRtjtjj|ƒƒnt|dƒjƒ|S)zX Create an empty file in the temporary directory, return the full path. Úa) ÚosÚpathÚjoinÚtempdirÚexistsÚdirnameÚmakedirsÚopenÚclose)ÚselfrZfpath©rú5/home/threebean/devel/bugwarrior/tests/test_config.pyÚcreate s zTestGetConfigPath.createcCs)|jdƒ}|jtjƒ|ƒdS)zX If it exists, use the file at $XDG_CONFIG_HOME/bugwarrior/bugwarriorrc z.config/bugwarrior/bugwarriorrcN)rÚ assertEqualsÚconfigÚget_config_path)rÚrcrrrÚ test_defaultszTestGetConfigPath.test_defaultcCs)|jdƒ}|jtjƒ|ƒdS)z: Falls back on .bugwarriorrc if it exists z .bugwarriorrcN)rrrr)rrrrrÚ test_legacyszTestGetConfigPath.test_legacycCs6|jdƒ|jdƒ}|jtjƒ|ƒdS)zY If both files above exist, the one in $XDG_CONFIG_HOME takes precedence z .bugwarriorrcz.config/bugwarrior/bugwarriorrcN)rrrr)rrrrrÚtest_xdg_first%s z TestGetConfigPath.test_xdg_firstcCs,|jtjƒtjj|jdƒƒdS)zf If no bugwarriorrc exist anywhere, the path to the prefered one is returned. z.config/bugwarrior/bugwarriorrcN)rrrrrr r )rrrrÚ test_no_file-s zTestGetConfigPath.test_no_filecCsYtjj|jdƒ}|tjd<|jdƒ|jdƒ|jtjƒ|ƒdS)z} If $BUGWARRIORRC is set, it takes precedence over everything else (even if the file doesn't exist). zmy-bugwarriorcÚ BUGWARRIORRCz .bugwarriorrcz.config/bugwarrior/bugwarriorrcN) rrr r Úenvironrrrr)rrrrrÚtest_BUGWARRIORRC6s    z#TestGetConfigPath.test_BUGWARRIORRCcCs6dtjd<|jdƒ}|jtjƒ|ƒdS)zp If $BUGWARRIORRC is set but emty, it is not used and the default file is used instead. Úrz.config/bugwarrior/bugwarriorrcN)rrrrrr)rrrrrÚtest_BUGWARRIORRC_emptyAs z)TestGetConfigPath.test_BUGWARRIORRC_emptyN) Ú__name__Ú __module__Ú __qualname__rrrrrrr rrrrr s     rcsReZdZ‡fdd†Zdd„Zdd„Zdd„Zd d „Z‡S) ÚTestGetDataPathcs6tt|ƒjƒtjƒ|_|jjdƒdS)NÚgeneral)Úsuperr$ÚsetUpÚ configparserÚRawConfigParserrÚ add_section)r)Ú __class__rrr'MszTestGetDataPath.setUpcCs#|j|tj|jdƒƒdS)Nr%)Ú assertEqualrÚ get_data_path)rZexpected_datapathrrrÚassertDataPathRszTestGetDataPath.assertDataPathcCs4tjj|jdƒ}tjd<|j|ƒdS)zX TASKDATA should be respected, even when taskrc's data.location is set. ÚdataÚTASKDATAN)rrr r rr.)rZdatapathrrrÚ test_TASKDATAVs#zTestGetDataPath.test_TASKDATAcCs!dtjd<|j|jƒdS)zX When TASKDATA is not set, data.location in taskrc should be respected. rr0N)rrr.Ú lists_path)rrrrÚtest_taskrc_datalocation]s z(TestGetDataPath.test_taskrc_datalocationc CsCt|jdƒWdQXdtjd<|jtjjdƒƒdS)zG When data path is not assigned, use default location. ÚwNrr0z~/.task)rÚtaskrcrrr.rÚ expanduser)rrrrÚtest_unassignedds zTestGetDataPath.test_unassigned)r!r"r#r'r.r1r3r7rr)r+rr$Ks    r$) Ú __future__rrr(Úbugwarrior.configrÚbaserrr$rrrrÚs   @bugwarrior-1.5.1/tests/__pycache__/test_data.cpython-34.pyc0000644000175000017500000000312213010652175025561 0ustar threebeanthreebean00000000000000î ´@#X)ã@s^ddlZddlZddlZddlmZddlmZGdd„deƒZdS)éN)Údataé)Ú ConfigTestcsFeZdZ‡fdd†Zdd„Zdd„Zdd„Z‡S) ÚTestDatacsEtt|ƒjƒtjƒ}|jdƒtj|dƒ|_dS)NÚgeneral)ÚsuperrÚsetUpÚ configparserÚRawConfigParserÚ add_sectionrÚBugwarriorData)ÚselfÚconfig)Ú __class__©ú3/home/threebean/devel/bugwarrior/tests/test_data.pyr s  zTestData.setUpcCs<ttj|jjƒjd@ƒ}|j|ddgƒdS)NiÿZ0600Z0o600)ÚoctÚosÚstatrÚdatafileÚst_modeÚassertIn)r Z permissionsrrrÚ assert0600s"zTestData.assert0600c Cs™t|jjdƒ}tjidd6|ƒWdQX|jjddƒ|j|jjdƒdƒ|j|jjƒidd6dd6ƒ|j ƒdS)Nzw+ÚstuffÚoldÚkeyÚvalue) ÚopenrrÚjsonÚdumpÚsetÚ assertEqualÚgetÚget_datar)r ÚhandlerrrÚ test_get_sets!zTestData.test_get_setcCs=|jjddƒ|j|jjdƒdƒ|jƒdS)Nrr)rr r!r"r)r rrrÚtest_set_first_time"szTestData.test_set_first_time)Ú__name__Ú __module__Ú __qualname__rrr%r&rr)rrr s   r)rrr Ú bugwarriorrÚbaserrrrrrÚs   bugwarrior-1.5.1/tests/__pycache__/test_trac.cpython-35.pyc0000664000175000017500000000667113061020516025612 0ustar threebeanthreebean00000000000000 ë ÄXû ã@s¥ddlmZddlmZddlmZddlmZmZGdd„deƒZGdd „d eƒZ Gd d „d eƒZ Gd d „d eeƒZ dS)é)Únext)Úobject)Ú TracServiceé)Ú ServiceTestÚAbstractServiceTestc@s"eZdZedd„ƒZdS)ÚFakeTracTicketcCsgS)N©)Z issuenumberr r ú3/home/threebean/devel/bugwarrior/tests/test_trac.pyÚ changeLog szFakeTracTicket.changeLogN)Ú__name__Ú __module__Ú __qualname__Ú staticmethodr r r r r rs rc@seZdZeƒZdS)ÚFakeTracServerN)r r rrÚticketr r r r rs rc@sCeZdZeƒZdd„Zedd„ƒZdd„ZdS)Ú FakeTracLibcCs ||_dS)N)Úrecord)Úselfrr r r Ú__init__szFakeTracLib.__init__cCsdgS)NÚ somethingr )Úqueryr r r Ú query_ticketsszFakeTracLib.query_ticketscCsddd|jfS)Nr)r)rrr r r Ú get_ticketszFakeTracLib.get_ticketN) r r rrÚserverrrrrr r r r rs   rc sˆeZdZddddddiZddd d d d d dddiZ‡fdd†Z‡fdd†Zdd„Zdd„Z‡S)Ú TestTracIssuez trac.base_urizhttp://ljlkajsdfl.comz trac.usernamerz trac.passwordZsomepwdÚurlzhttp://some/url.com/Úsummaryz Some SummaryÚnumberéÌÚpriorityÚcriticalÚ componentÚ testcomponentcs)tt|ƒjƒ|jtƒ|_dS)N)ÚsuperrÚsetUpÚget_mock_servicerÚservice)r)Ú __class__r r r%.szTestTracIssue.setUpcs1tt|ƒj||Ž}t|jƒ|_|S)N)r$rr&rÚarbitrary_issueZtrac)rÚargsÚkwargsr')r(r r r&2szTestTracIssue.get_mock_servicecCs¾dddgddi}|jj|j|ƒ}d|dd|j|jdd|d|j|jd|j|jd|j|jd |j|jd i}|jƒ}|j ||ƒdS) NÚ annotationsÚalphaÚbetaÚprojectz some projectr rrrr") r'Úget_issue_for_recordr)Ú PRIORITY_MAPÚURLÚSUMMARYÚNUMBERZ COMPONENTÚto_taskwarriorÚ assertEquals)rÚarbitrary_extraÚissueÚexpected_outputÚ actual_outputr r r Útest_to_taskwarrior7s       z!TestTracIssue.test_to_taskwarriorcCskt|jjƒƒ}dgdddddddgd d d d d dddi }|j|jƒ|ƒdS)Nr,Ú descriptionzA(bw)Is#1 - Some Summary .. https://http://ljlkajsdfl.com/ticket/1r ÚHr/Ú unspecifiedÚtagsZ tracnumberrZ tracsummaryz Some SummaryZtracurlz&https://http://ljlkajsdfl.com/ticket/1Z traccomponentr#)rr'ÚissuesÚ assertEqualÚget_taskwarrior_record)rr8Úexpectedr r r Ú test_issuesRs zTestTracIssue.test_issues) r r rÚSERVICE_CONFIGr)r%r&r;rDr r )r(r r s    rN) ÚbuiltinsrrZbugwarrior.services.tracrÚbaserrrrrrr r r r Úsbugwarrior-1.5.1/tests/__pycache__/test_config.cpython-35.pyc0000644000175000017500000002010113064557522026123 0ustar threebeanthreebean00000000000000 ÑÚÑX&ã@sÉddlmZddlZddlZddlmZddljZddlm Z Gdd„de ƒZ Gdd „d e ƒZ Gd d „d eƒZ Gd d „d eƒZ Gdd„deƒZdS)é)Úunicode_literalsN)ÚTestCaseé)Ú ConfigTestc@sdeZdZdd„Zdd„Zdd„Zdd„Zd d „Zd d „Zd d„Z dS)ÚTestGetConfigPathcCsftjj|j|ƒ}tjjtjj|ƒƒsOtjtjj|ƒƒt|dƒjƒ|S)zX Create an empty file in the temporary directory, return the full path. Úa) ÚosÚpathÚjoinÚtempdirÚexistsÚdirnameÚmakedirsÚopenÚclose)Úselfr Zfpath©rú5/home/threebean/devel/bugwarrior/tests/test_config.pyÚcreates zTestGetConfigPath.createcCs)|jdƒ}|jtjƒ|ƒdS)zX If it exists, use the file at $XDG_CONFIG_HOME/bugwarrior/bugwarriorrc z.config/bugwarrior/bugwarriorrcN)rÚ assertEqualsÚconfigÚget_config_path)rÚrcrrrÚ test_defaultszTestGetConfigPath.test_defaultcCs)|jdƒ}|jtjƒ|ƒdS)z: Falls back on .bugwarriorrc if it exists z .bugwarriorrcN)rrrr)rrrrrÚ test_legacy szTestGetConfigPath.test_legacycCs6|jdƒ|jdƒ}|jtjƒ|ƒdS)zY If both files above exist, the one in $XDG_CONFIG_HOME takes precedence z .bugwarriorrcz.config/bugwarrior/bugwarriorrcN)rrrr)rrrrrÚtest_xdg_first's z TestGetConfigPath.test_xdg_firstcCs,|jtjƒtjj|jdƒƒdS)zf If no bugwarriorrc exist anywhere, the path to the prefered one is returned. z.config/bugwarrior/bugwarriorrcN)rrrrr r r )rrrrÚ test_no_file/s zTestGetConfigPath.test_no_filecCsYtjj|jdƒ}|tjd<|jdƒ|jdƒ|jtjƒ|ƒdS)z} If $BUGWARRIORRC is set, it takes precedence over everything else (even if the file doesn't exist). zmy-bugwarriorcÚ BUGWARRIORRCz .bugwarriorrcz.config/bugwarrior/bugwarriorrcN) rr r r Úenvironrrrr)rrrrrÚtest_BUGWARRIORRC8s    z#TestGetConfigPath.test_BUGWARRIORRCcCs6dtjd<|jdƒ}|jtjƒ|ƒdS)zp If $BUGWARRIORRC is set but emty, it is not used and the default file is used instead. Úrz.config/bugwarrior/bugwarriorrcN)rrrrrr)rrrrrÚtest_BUGWARRIORRC_emptyCs z)TestGetConfigPath.test_BUGWARRIORRC_emptyN) Ú__name__Ú __module__Ú __qualname__rrrrrrr!rrrrr s     rcsReZdZ‡fdd†Zdd„Zdd„Zdd„Zd d „Z‡S) ÚTestGetDataPathcs6tt|ƒjƒtjƒ|_|jjdƒdS)NÚgeneral)Úsuperr%ÚsetUpÚ configparserÚRawConfigParserrÚ add_section)r)Ú __class__rrr(OszTestGetDataPath.setUpcCs#|j|tj|jdƒƒdS)Nr&)Ú assertEqualrÚ get_data_path)rZexpected_datapathrrrÚassertDataPathTszTestGetDataPath.assertDataPathcCs4tjj|jdƒ}tjd<|j|ƒdS)zX TASKDATA should be respected, even when taskrc's data.location is set. ÚdataÚTASKDATAN)rr r r rr/)rZdatapathrrrÚ test_TASKDATAXs#zTestGetDataPath.test_TASKDATAcCs!dtjd<|j|jƒdS)zX When TASKDATA is not set, data.location in taskrc should be respected. r r1N)rrr/Ú lists_path)rrrrÚtest_taskrc_datalocation_s z(TestGetDataPath.test_taskrc_datalocationc CsDt|jdƒWdQRXdtjd<|jtjjdƒƒdS)zG When data path is not assigned, use default location. ÚwNr r1z~/.task)rÚtaskrcrrr/r Ú expanduser)rrrrÚtest_unassignedfs zTestGetDataPath.test_unassigned)r"r#r$r(r/r2r4r8rr)r,rr%Ms    r%c@seZdZdd„ZdS)ÚTestOracleEvalcCs|jtjdƒdƒdS)Nuecho fööbÃ¥ru fööbÃ¥r)r-rÚ oracle_eval)rrrrÚ test_echouszTestOracleEval.test_echoN)r"r#r$r;rrrrr9ss r9c@s@eZdZdd„Zdd„Zdd„Zdd„Zd S) ÚTestBugwarriorConfigParsercCsetjƒ|_|jjdƒ|jjdddƒ|jjdddƒ|jjdddƒdS)Nr&ÚsomeintÚ4Úsomenoner ÚsomecharÚ somestring)rÚBugwarriorConfigParserr+Úset)rrrrr(zs z TestBugwarriorConfigParser.setUpcCs#|j|jjddƒdƒdS)Nr&r=é)r-rÚgetint)rrrrÚ test_getintsz&TestBugwarriorConfigParser.test_getintcCs#|j|jjddƒdƒdS)Nr&r?)r-rrE)rrrrÚtest_getint_none„sz+TestBugwarriorConfigParser.test_getint_nonec Cs.|jtƒ|jjddƒWdQRXdS)Nr&r@)Ú assertRaisesÚ ValueErrorrrE)rrrrÚtest_getint_valueerror‡sz1TestBugwarriorConfigParser.test_getint_valueerrorN)r"r#r$r(rFrGrJrrrrr<ys    r<c@sdeZdZdd„Zdd„Zdd„Zdd„Zd d „Zd d „Zd d„Z dS)ÚTestServiceConfigcCs±d|_tjƒ|_|jj|jƒ|jj|jddƒ|jj|jddƒ|jj|jddƒ|jj|jdd ƒtjd |j|jƒ|_dS) NZ someservicezsomeprefix.someintr>zsomeprefix.somenoner zsomeprefix.somecharrAzsomeprefix.someboolÚtrueZ someprefix)ÚtargetrrBr+rCÚ ServiceConfigÚservice_config)rrrrr(s zTestServiceConfig.setUpcCs#|j|jj|jdƒƒdS)zY Methods not defined in ServiceConfig should be proxied to configparser. zsomeprefix.someintN)Ú assertTruerOÚ has_optionrM)rrrrÚtest_configparser_proxyšsz)TestServiceConfig.test_configparser_proxycCs|jd|jkƒdS)Nr=)rPrO)rrrrÚtest__contains__¡sz"TestServiceConfig.test__contains__cCs |j|jjdƒdƒdS)Nr=r>)r-rOÚget)rrrrÚtest_get¤szTestServiceConfig.test_getcCs&|j|jjdddƒdƒdS)NÚ someoptionÚdefaultZ somedefault)r-rOrT)rrrrÚtest_get_default§sz"TestServiceConfig.test_get_defaultcCs|j|jjdƒƒdS)NrV)Ú assertIsNonerOrT)rrrrÚtest_get_default_none­sz'TestServiceConfig.test_get_default_nonecCs)|j|jjddtjƒdƒdS)NZsomeboolÚto_typeT)ÚassertIsrOrTrÚasbool)rrrrÚtest_get_to_type°sz"TestServiceConfig.test_get_to_typeN) r"r#r$r(rRrSrUrXrZr^rrrrrKŒs      rK)Ú __future__rrr)ÚunittestrÚbugwarrior.configrÚbaserrr%r9r<rKrrrrÚs  @&bugwarrior-1.5.1/tests/__pycache__/__init__.cpython-35.pyc0000644000175000017500000000020513050354041025343 0ustar threebeanthreebean00000000000000 ¿(ETã@sdS)N©rrrú2/home/threebean/devel/bugwarrior/tests/__init__.pyÚsbugwarrior-1.5.1/tests/__pycache__/test_taiga.cpython-34.pyc0000644000175000017500000000475113010652175025746 0ustar threebeanthreebean00000000000000î ´@#XÒ ã@s_ddlmZddlZddlmZddlmZmZGdd„deeƒZdS)é)ÚnextN)Ú TaigaServiceé)Ú ServiceTestÚAbstractServiceTestcsƒeZdZidd6dd6Zidd6dd6d d 6d d 6d gd6Z‡fdd†Zdd„Zejdd„ƒZ ‡S)ÚTestTaigaIssuez https://oneztaiga.base_uriÚtwoztaiga.auth_tokeniÚidéÚprojecté(Úrefzthis is a titleÚsubjectÚ bugwarriorÚtagscs)tt|ƒjƒ|jtƒ|_dS)N)ÚsuperrÚsetUpÚget_mock_servicerÚservice)Úself)Ú __class__©ú4/home/threebean/devel/bugwarrior/tests/test_taiga.pyrszTestTaigaIssue.setUpcCsidd6gd6dd6}|jj|j|ƒ}|jƒ}igd6dd6dd6dgd 6d d 6d d 6dd6}|j||ƒdS)NZawesomer Ú annotationsz this is a urlÚurlÚMÚpriorityrrr Útaigaidzthis is a titleÚ taigasummaryÚtaigaurl)rÚget_issue_for_recordÚrecordÚto_taskwarriorÚ assertEqual)rÚextraÚissueÚactualÚexpectedrrrÚtest_to_taskwarriors    z"TestTaigaIssue.test_to_taskwarriorcCsd}|jddi|d6ƒ|jdj|ƒd|jgƒ|jdj|jdƒdidd 6ƒ|jd j|jdƒdiid d 6d 6dd6gƒt|jjƒƒ}idgd6dd6dd6dd6dgd6dd6dd6dd6}|j|jƒ|ƒdS)Nrzhttps://one/api/v1/users/meÚjsonr zFhttps://one/api/v1/userstories?status__is_closed=false&assigned_to={0}zhttps://one/api/v1/projects/{0}r Ú somethingZslugz(https://one/api/v1/history/userstory/{0}ZyouÚusernameÚuserzBlah blah blah!Úcommentz@you - Blah blah blah!rzB(bw)Is#40 - this is a title .. https://one/project/something/us/40Ú descriptionrrrrr rzthis is a titlerz#https://one/project/something/us/40r)Ú add_responseÚformatr!rrÚissuesr#Úget_taskwarrior_record)rÚuseridr%r'rrrÚ test_issues3s4     zTestTaigaIssue.test_issues) Ú__name__Ú __module__Ú __qualname__ÚSERVICE_CONFIGr!rr(Ú responsesÚactivater4rr)rrr s    r) Úbuiltinsrr9Zbugwarrior.services.taigarÚbaserrrrrrrÚs bugwarrior-1.5.1/tests/__pycache__/test_github.cpython-35.pyc0000664000175000017500000002504113064355425026150 0ustar threebeanthreebean00000000000000 ÑÚÑXþ(ã@sËddlmZddlZddlmZddlmZddlZddlZddl m Z ddl m Z m Z ddlmZmZejjƒejd dƒjd ejd dƒZejjƒjd ejd dƒZd d dddddddddddidd didddigdejƒdejƒd d!i Zd"d#d$d%d&giZGd'd(„d(eeƒZGd)d*„d*eeƒZGd+d,„d,eƒZGd-d.„d.eƒZdS)/é)ÚnextN)ÚTestCase)ÚRawConfigParser)Ú ServiceConfig)Ú GithubServiceÚ GithubClienté)Ú ServiceTestÚAbstractServiceTestÚhoursÚtzinfoÚ microsecondÚtitleÚHalloÚhtml_urlz;https://github.com/arbitrary_username/arbitrary_repo/pull/1ÚurlzGhttps://api.github.com/repos/arbitrary_username/arbitrary_repo/issues/1Únumberé ÚbodyÚ SomethingÚuserÚloginÚarbitrary_loginÚ milestoneÚalphaÚlabelsÚnameÚbugfixZ created_atZ updated_atÚrepoz!arbitrary_username/arbitrary_repoÚprojectZoneÚtypeÚissueÚ annotationscsmeZdZdZddddddiZ‡fdd †Zd d „Zd d „Zej dd„ƒZ ‡S)ÚTestGithubIssueNz github.loginrzgithub.passwordÚarbitrary_passwordzgithub.usernameÚarbitrary_usernamecs)tt|ƒjƒ|jtƒ|_dS)N)Úsuperr#ÚsetUpÚget_mock_servicerÚservice)Úself)Ú __class__©ú5/home/threebean/devel/bugwarrior/tests/test_github.pyr'0szTestGithubIssue.setUpcCs2|jjttƒ}|j|jdƒdƒdS)Nz needs workZ needs_work)r)Úget_issue_for_recordÚARBITRARY_ISSUEÚARBITRARY_EXTRAÚ assertEqualZ_normalize_label_to_tag)r*r!r,r,r-Útest_normalize_label_to_tag4s   z+TestGithubIssue.test_normalize_label_to_tagcCsîd|j_|jjttƒ}dtdd|jjdgddg|jtd|jtd|jtd |j td |j td |j t |j t|jtd |jtd d |jtddi}|jƒ}|j||ƒdS)NTrÚpriorityr"Útagsrrrr rrrrrr)r)Zimport_labels_as_tagsr.r/r0Údefault_priorityÚURLZREPOÚTYPEÚTITLEÚNUMBERZ UPDATED_ATÚARBITRARY_UPDATEDZ CREATED_ATÚARBITRARY_CREATEDÚBODYÚ MILESTONEÚUSERÚto_taskwarriorr1)r*r!Úexpected_outputÚ actual_outputr,r,r-Útest_to_taskwarrior<s(               z#TestGithubIssue.test_to_taskwarriorcCs6|jdddddddiigƒ|jdddd ddd iigƒ|jd dtgƒ|jd dtgƒ|jd ddddiddigƒt|jjƒƒ}ddgdddddtdddddddd d!d"d#td$d%d&dd'd(d)d d*gi}|j|jƒ|ƒdS)+Nz.https://api.github.com/user/repos?per_page=100ÚjsonrZ some_repoÚownerrZ some_usernamezBhttps://api.github.com/users/arbitrary_username/repos?per_page=100Úarbitrary_repor%zRhttps://api.github.com/repos/arbitrary_username/arbitrary_repo/issues?per_page=100z/https://api.github.com/user/issues?per_page=100z^https://api.github.com/repos/arbitrary_username/arbitrary_repo/issues/10/comments?per_page=100rrrzArbitrary comment.r"z%@arbitrary_login - Arbitrary comment.Ú descriptionzP(bw)Is#10 - Hallo .. https://github.com/arbitrary_username/arbitrary_repo/pull/1Ú githubbodyrÚgithubcreatedonÚgithubmilestonerÚ githubnumberrÚ githubrepoz!arbitrary_username/arbitrary_repoÚ githubtitlerÚ githubtyper!ÚgithubupdatedatÚ githuburlz;https://github.com/arbitrary_username/arbitrary_repo/pull/1Ú githubuserr3ÚMrr4) Ú add_responser/rr)Úissuesr;r:r1Úget_taskwarrior_record)r*r!Úexpectedr,r,r-Ú test_issuesWsF     zTestGithubIssue.test_issues) Ú__name__Ú __module__Ú __qualname__ÚmaxDiffÚSERVICE_CONFIGr'r2rBÚ responsesÚactivaterVr,r,)r+r-r#(s    r#c sseZdZdZdddddddd d d d d iZ‡fd d†Zdd„Zejdd„ƒZ ‡S)ÚTestGithubIssueQueryNz github.loginrzgithub.passwordr$zgithub.usernamer%z github.queryzis:open reviewer:octocatzgithub.include_user_reposÚFalsezgithub.include_user_issuescs)tt|ƒjƒ|jtƒ|_dS)N)r&r^r'r(rr))r*)r+r,r-r'—szTestGithubIssueQuery.setUpcCsdS)Nr,)r*r,r,r-rB›sz(TestGithubIssueQuery.test_to_taskwarriorcCsÚ|jdddtgiƒ|jdddddidd igƒt|jjƒƒd }d d gd ddddtdddddddddddtddddd d!d"d#d$gi}|j|jƒ|ƒdS)%NzPhttps://api.github.com/search/issues?q=is%3Aopen+reviewer%3Aoctocat&per_page=100rCÚitemsz^https://api.github.com/repos/arbitrary_username/arbitrary_repo/issues/10/comments?per_page=100rrrrzArbitrary comment.rr"z%@arbitrary_login - Arbitrary comment.rFzP(bw)Is#10 - Hallo .. https://github.com/arbitrary_username/arbitrary_repo/pull/1rGrrHrIrrJrrKz!arbitrary_username/arbitrary_reporLrrMr!rNrOz;https://github.com/arbitrary_username/arbitrary_repo/pull/1rPr3rQrrEr4) rRr/Úlistr)rSr;r:r1rT)r*r!rUr,r,r-rVžs0   z TestGithubIssueQuery.test_issues) rWrXrYrZr[r'rBr\r]rVr,r,)r+r-r^Œs   r^c@s|eZdZdd„Zdd„Zdd„Zdd„Zd d „Zd d „Zd d„Z dd„Z dd„Z dS)ÚTestGithubServicecCs¯tƒ|_d|j_|jjdƒ|jjdƒ|jjdddƒ|jjdddƒ|jjddd ƒ|jjdd d ƒttj|jdƒ|_dS) NFÚgeneralÚmygithubr)Úgithubz github.loginÚtintinzgithub.usernameZmilouzgithub.passwordZ t0ps3cr3t) rÚconfigÚ interactiveÚ add_sectionÚsetrrÚ CONFIG_PREFIXÚservice_config)r*r,r,r-r'Ãs  zTestGithubService.setUpcCs_|jjddƒ|jjdddƒt|jddƒ}|j|jjjddƒdS)Nrdzgithub.passwordz github.tokenz"@oracle:eval:echo 1234567890ABCDEFrcÚ Authorizationztoken 1234567890ABCDEF)rgÚ remove_optionrjrr1ÚclientÚsessionÚheaders)r*r)r,r,r-Útest_token_authorization_headerÏs z1TestGithubService.test_token_authorization_headercCs,t|jddƒ}|jd|jƒdS)z@ Check that if github.host is not set, we default to github.com rcrdz github.comN)rrgÚ assertEqualsÚhost)r*r)r,r,r-Útest_default_host×sz#TestGithubService.test_default_hostcCsB|jjdddƒt|jddƒ}|jd|jƒdS)z< Check that if github.host is set, we use its value as host rdz github.hostzgithub.example.comrcN)rgrjrrsrt)r*r)r,r,r-Útest_overwrite_hostÜsz%TestGithubService.test_overwrite_hostcCs&tj|jƒ}|jd|ƒdS)z& Checks that the keyring service name z github://tintin@github.com/milouN)rÚget_keyring_servicerlrs)r*Úkeyring_servicer,r,r-Útest_keyring_serviceâsz&TestGithubService.test_keyring_servicecCs<|jjdddƒtj|jƒ}|jd|ƒdS)z9 Checks that the keyring key depends on the github host. rdz github.hostzgithub.example.comz(github://tintin@github.example.com/milouN)rgrjrrwrlrs)r*rxr,r,r-Útest_keyring_service_hostçsz+TestGithubService.test_keyring_service_hostcCs2tddƒ}tj|ƒ}|jd|ƒdS)NÚ repos_urlzhttps://github.com/foo/barzfoo/bar)ÚdictrÚget_repository_from_issuers)r*r!Ú repositoryr,r,r-Ú)test_get_repository_from_issue_url__issueísz;TestGithubService.test_get_repository_from_issue_url__issuecCs2tddƒ}tj|ƒ}|jd|ƒdS)Nr{zhttps://github.com/foo/barzfoo/bar)r|rr}rs)r*r!r~r,r,r-Ú0test_get_repository_from_issue_url__pull_requestòszBTestGithubService.test_get_repository_from_issue_url__pull_requestcCs2tddƒ}tj|ƒ}|jd|ƒdS)Nr{zhttps://github.acme.biz/foo/barzfoo/bar)r|rr}rs)r*r!r~r,r,r-Ú1test_get_repository_from_issue__enterprise_github÷szCTestGithubService.test_get_repository_from_issue__enterprise_githubN) rWrXrYr'rrrurvryrzrr€rr,r,r,r-rbÁs        rbc@s4eZdZdd„Zdd„Zdd„ZdS)ÚTestGithubClientcCs8ddi}td|ƒ}|j|jdƒdƒdS)NÚtokenÚxxxxz github.comz /some/pathz https://api.github.com/some/path)rrsÚ_api_url)r*Úauthror,r,r-Ú test_api_urlÿs zTestGithubClient.test_api_urlcCs>ddi}td|ƒ}|j|jdddƒdƒdS)Nrƒr„z github.comz/some/path/{foo}ÚfooÚbarz$https://api.github.com/some/path/bar)rrsr…)r*r†ror,r,r-Útest_api_url_with_contexts  z*TestGithubClient.test_api_url_with_contextcCs8ddi}td|ƒ}|j|jdƒdƒdS)z/ Test generating an API URL with a custom host rƒr„zgithub.example.comz /some/pathz+https://github.example.com/api/v3/some/pathN)rrsr…)r*r†ror,r,r-Útest_api_url_with_custom_host s   z.TestGithubClient.test_api_url_with_custom_hostN)rWrXrYr‡rŠr‹r,r,r,r-r‚ýs   r‚)ÚbuiltinsrÚdatetimeÚunittestrÚ configparserrÚpytzr\Úbugwarrior.configrZbugwarrior.services.githubrrÚbaser r ÚutcnowÚ timedeltaÚreplaceÚUTCr;r:Ú isoformatr/r0r#r^rbr‚r,r,r,r-Ús<   "      d5<bugwarrior-1.5.1/tests/__pycache__/test_templates.cpython-34.pyc0000644000175000017500000000574113010652175026657 0ustar threebeanthreebean00000000000000î ´@#X ã@s:ddlmZddlmZGdd„deƒZdS)é)ÚIssueé)Ú ServiceTestcsjeZdZ‡fdd†Zdddddd„Zdd„Zdd „Zd d „Zd d „Z‡S)Ú TestTemplatescs7tt|ƒjƒd|_idd6dd6|_dS)NzConstruct Library on TerminusZ end_of_empireÚprojectÚHÚpriority)ÚsuperrÚsetUpÚarbitrary_default_descriptionÚarbitrary_issue)Úself)Ú __class__©ú8/home/threebean/devel/bugwarrior/tests/test_templates.pyr s  zTestTemplates.setUpNcs—|dkrin|}idd6dd6dd6|d6dd6|rJ|ngd 6}ti|ƒ}‡‡fd d †|_‡‡fd d †|_|S) NédÚannotation_lengthrÚdefault_priorityÚdescription_lengthÚ templatesFÚshortenÚadd_tagscsˆdkrˆjSˆS)N)r r)Ú descriptionr rrÚsz)TestTemplates.get_issue..csˆdkrˆjSˆS)N)r r)rr rrr s)rÚto_taskwarriorÚget_default_description)r rÚissuerrÚoriginr)rr rÚ get_issueszTestTemplates.get_issuecCs\|jiƒ}|jƒ}|jjƒ}|ji|jd6gd6ƒ|j||ƒdS)NrÚtags)rÚget_taskwarrior_recordr ÚcopyÚupdater Ú assertEqual)r rÚrecordÚexpected_recordrrrÚtest_default_taskwarrior_record&s    z-TestTemplates.test_default_taskwarrior_recordcCszd}|ji|d6ƒ}|jƒ}|jjƒ}|jid|jd|jfd6gd6ƒ|j||ƒdS)Nz"{{ priority }} - {{ description }}rz%s - %srr)rr r r!r"r r#)r Zdescription_templaterr$r%rrrÚtest_override_description2s      z'TestTemplates.test_override_descriptioncCsd}|ji|d6ƒ}|jƒ}|jjƒ}|ji|jd6d|jdjƒd6gd6ƒ|j||ƒdS)Nzwat_{{ project|upper }}rrzwat_%sr)rr r r!r"r Úupperr#)r Zproject_templaterr$r%rrrÚtest_override_projectEs      z#TestTemplates.test_override_projectcCsr|jdddgƒ}|jƒ}|jjƒ}|ji|jd6d|jdgd6ƒ|j||ƒdS)NrÚonez {{ project }}rrr)rr r r!r"r r#)r rr$r%rrrÚtest_tag_templatesVs   z TestTemplates.test_tag_templates) Ú__name__Ú __module__Ú __qualname__r rr&r'r)r+rr)rrrs    rN)Úbugwarrior.servicesrÚbaserrrrrrÚsbugwarrior-1.5.1/tests/__pycache__/test_activecollab2.cpython-35.pyc0000644000175000017500000000624713050354041027371 0ustar threebeanthreebean00000000000000 ´@#X† ã@sƒddlmZddlZddlZddlZddlZddlmZddlm Z m Z Gdd„de e ƒZ dS)é)ÚnextN)ÚActiveCollab2Serviceé)Ú ServiceTestÚAbstractServiceTestcs0eZdZddddddddiZejjƒejd d ƒjd ej ƒZ ejjƒejd d ƒjd ej ƒZ d ddd de j ƒddddddddde j ƒddddddd d!edd"d#igd$d%i Z ‡fd&d'†Zd(d)„Zejd*d+„ƒZ‡S),ÚTestActiveCollab2Issuezactivecollab2.urlz http://hellozactivecollab2.keyÚhowdyzactivecollab2.user_idrzactivecollab2.projectsz 1:one, 2:twoÚhoursrÚtzinfoéÚprojectÚ somethingÚpriorityÚdue_onÚ permalinkzhttp://wherever/Ú ticket_idé Ú project_idéÚtypeÚTicketÚ created_onÚ created_by_idÚ10Úbodyz Ticket BodyÚnameÚ AnonymousZ assigneesÚuser_idZis_ownerTÚ descriptionzFurther detail.cs)tt|ƒjƒ|jtƒ|_dS)N)ÚsuperrÚsetUpÚget_mock_servicerÚservice)Úself)Ú __class__©ús    bugwarrior-1.5.1/tests/__pycache__/test_megaplan.cpython-34.pyc0000644000175000017500000000655213010652175026446 0ustar threebeanthreebean00000000000000î ´@#X` ã @s¹ddlmZddlmZddlZddlZyddlmZWn!ek roejdƒ‚YnXddl m Z m Z Gdd „d eƒZ Gd d „d e e ƒZ dS) é)Únext)ÚobjectN)ÚMegaplanServicez6Upstream python-megaplan does not support python3 yet.é)Ú ServiceTestÚAbstractServiceTestc@s(eZdZdd„Zdd„ZdS)ÚFakeMegaplanClientcCs ||_dS)N)Úrecord)Úselfr ©r ú7/home/threebean/devel/bugwarrior/tests/test_megaplan.pyÚ__init__szFakeMegaplanClient.__init__cCs |jgS)N)r )r r r r Úget_actual_taskssz#FakeMegaplanClient.get_actual_tasksN)Ú__name__Ú __module__Ú __qualname__r rr r r r rs  rcs“eZdZidd6dd6dd6Zddd gZid d 6d jeƒd 6Z‡fdd†Z‡fdd†Zdd„Z dd„Z ‡S)ÚTestMegaplanIssueÚ somethingzmegaplan.hostnameZsomething_elsezmegaplan.loginZaljlkjzmegaplan.passwordÚoneÚtwoÚthreeé ÚIdú|ÚNamec s?tt|ƒjƒtjdƒ|jtƒ|_WdQXdS)Nzmegaplan.Client)ÚsuperrÚsetUpÚmockÚpatchÚget_mock_servicerÚservice)r )Ú __class__r r r"szTestMegaplanIssue.setUpcs1tt|ƒj||Ž}t|jƒ|_|S)N)rrrrÚarbitrary_issueÚclient)r ÚargsÚkwargsr )r!r r r's z"TestMegaplanIssue.get_mock_servicec sòd‰d‰|jj|jƒ}iˆd6|jjd6|jd|j6ˆ|j6|jd |j6}‡fdd†}‡fd d †}tj j |d tj d tj ƒ-}||d _ ||d _ |j ƒ}WdQX|j||ƒdS)Nrzhttp://one.com/ÚprojectÚpriorityrrcsˆS)Nr )r$)Ú arbitrary_urlr r Úget_url<sz6TestMegaplanIssue.test_to_taskwarrior..get_urlcsˆS)Nr )r$)Úarbitrary_projectr r Ú get_project?sz:TestMegaplanIssue.test_to_taskwarrior..get_projectr+Z get_issue_urléÿÿÿÿ)r Úget_issue_for_recordr"Údefault_priorityÚ FOREIGN_IDÚURLÚ name_partsÚTITLErrÚmultipleÚDEFAULTÚ side_effectÚto_taskwarriorÚ assertEqual)r ÚissueÚexpected_outputr)r+ZmockedÚ actual_outputr )r*r(r Útest_to_taskwarrior-s$     z%TestMegaplanIssue.test_to_taskwarriorcCsft|jjƒƒ}idd6dd6dd6dd6d d 6d d 6gd 6}|j|jƒ|ƒdS)Nz4(bw)Is#10 - three .. https://something/task/10/card/Ú descriptionrZ megaplanidrZ megaplantitlezhttps://something/task/10/card/Z megaplanurlÚMr'rr&Útags)rr Úissuesr7Úget_taskwarrior_record)r r8Úexpectedr r r Ú test_issuesKs zTestMegaplanIssue.test_issues) rrrÚSERVICE_CONFIGr1Újoinr"rrr;rBr r )r!r rs   r)ÚbuiltinsrrrÚunittestZbugwarrior.services.mplanrÚ SyntaxErrorÚSkipTestÚbaserrrrr r r r Ús   bugwarrior-1.5.1/tests/__pycache__/test_jira.cpython-34.pyc0000644000175000017500000000733113010652175025603 0ustar threebeanthreebean00000000000000î ´@#X` ã@s•ddlmZddlmZddlmZddlZddlmZddlm Z m Z Gdd „d eƒZ Gd d „d e e ƒZ dS) é)Únext)Úobject)Ú namedtupleN)Ú JiraServiceé)Ú ServiceTestÚAbstractServiceTestc@s4eZdZdd„Zdd„Zdd„ZdS)ÚFakeJiraClientcCs ||_dS)N)Úarbitrary_record)Úselfr ©r ú3/home/threebean/devel/bugwarrior/tests/test_jira.pyÚ__init__ szFakeJiraClient.__init__cOs/tdddgƒ}||j|jdƒgS)NÚCaseÚrawÚkey)rr )r ÚargsÚkwargsrr r r Ú search_issuesszFakeJiraClient.search_issuescOsdS)Nr )r rrr r r ÚcommentsszFakeJiraClient.commentsN)Ú__name__Ú __module__Ú __qualname__rrrr r r r r s   r csÊeZdZidd6dd6dd6ZdZdZd Zd Ziid d 6ed 6ed6dd6idd6gd6d6deefd6Z‡fdd†Z ‡fdd†Z dd„Z dd„Z ‡S)Ú TestJiraIssueÚonez jira.usernameÚtwoz jira.base_uriÚthreez jira.passwordiÚ10ÚDONUTÚ lkjaldsfjaldfZBlockerÚpriorityÚsummaryZ timeestimatez2016-06-06T06:07:08.123-0700Úcreatedz1.2.3ÚnameZ fixVersionsÚfieldsz%s-%src s?tt|ƒjƒtjdƒ|jtƒ|_WdQXdS)Nzjira.client.JIRA._get_json)ÚsuperrÚsetUpÚmockÚpatchÚget_mock_servicerÚservice)r )Ú __class__r r r&.szTestJiraIssue.setUpcs1tt|ƒj||Ž}t|jƒ|_|S)N)r%rr)r r Újira)r rrr*)r+r r r)3szTestJiraIssue.get_mock_servicec sd‰idd6dgd6}|jj|j|ƒ}i |jd6|j|jddd6|dd6gd 6d d 6d d 6ˆ|j6|jd|j6|j|j6d|j 6|j dd|j 6}‡fdd†}t j j|dd|ƒ|jƒ}WdQX|j||ƒdS)Nz http://oneéZ jira_versionz an annotationÚ annotationsÚprojectr$r Útagsz20160606T060708-0700Úentryz1.2.3Újirafixversionré<csˆS)Nr )r)Ú arbitrary_urlr r Úget_urlTsz2TestJiraIssue.test_to_taskwarrior..get_urlr5Ú side_effect)r*Úget_issue_for_recordr Úarbitrary_projectÚ PRIORITY_MAPÚURLÚ FOREIGN_IDÚarbitrary_summaryÚSUMMARYÚ DESCRIPTIONÚarbitrary_estimationZESTIMATEr'r(rÚto_taskwarriorÚ assertEqual)r Úarbitrary_extraÚissueÚexpected_outputr5Ú actual_outputr )r4r Útest_to_taskwarrior8s,       z!TestJiraIssue.test_to_taskwarriorcCs‰t|jjƒƒ}i gd6dd6dd6dd6dd6d d 6d d 6d d6dd6dd6dd6gd6}|j|jƒ|ƒdS)Nr.z0(bw)Is#10 - lkjaldsfjaldf .. two/browse/DONUT-10Ú descriptionz20160606T060708-0700r1ZjiradescriptionrZ jiraestimatez1.2.3r2zDONUT-10ZjiraidrZ jirasummaryztwo/browse/DONUT-10ZjiraurlÚHr rr/r0)rr*ÚissuesrAÚget_taskwarrior_record)r rCÚexpectedr r r Ú test_issues\s zTestJiraIssue.test_issues) rrrÚSERVICE_CONFIGr?Z arbitrary_idr8r<r r&r)rFrLr r )r+r rs(   $r) ÚbuiltinsrrÚ collectionsrr'Zbugwarrior.services.jirarÚbaserrr rr r r r Ús  bugwarrior-1.5.1/tests/__pycache__/test_trello.cpython-34.pyc0000644000175000017500000002262313010652175026160 0ustar threebeanthreebean00000000000000î ´@#X»"ã@sÄddlmZmZddlmZejƒddlmZddlZddl m Z ddl Z ddl m Z mZddlmZmZGd d „d eƒZGd d „d eƒZdS) é)Úunicode_literalsÚprint_function)Ústandard_library)ÚnextN)Úpatch)Ú TrelloServiceÚ TrelloIssueé)Ú ConfigTestÚ ServiceTestcs…eZdZidd6dd6dd6dd6d d 6d d 6id d6idd6gd6Z‡fdd†Zdd„Zdd„Z‡S)ÚTestTrelloIssueZ542bbb6583d705eb05bbe491Úidé*ÚidShortz%So long, and thanks for all the fish!ÚnameZAAaaBBbbÚ shortLinkzhttps://trello.com/c/AAaaBBbbÚshortUrlz'https://trello.com/c/AAaBBbb/42-so-longÚurlÚfooÚbarÚlabelscs^tt|ƒjƒtddddddƒ}idd6dd 6}t|j||ƒ|_dS) NÚ inline_linksTÚdescription_lengthéÚimport_labels_as_tagszHyperspatial express routeZ boardnameÚ SomethingZlistname)Úsuperr ÚsetUpÚdictrÚJSONÚissue)ÚselfÚoriginÚextra)Ú __class__©ú5/home/threebean/devel/bugwarrior/tests/test_trello.pyrs    zTestTrelloIssue.setUpcCs#d}|j||jjƒƒdS)z Test the generated description zJ(bw)#42 - So long, and thanks for all the .. https://trello.com/c/AAaaBBbbN)Ú assertEqualr Úget_default_description)r!Z expected_descr%r%r&Útest_default_description!sz(TestTrelloIssue.test_default_descriptioncCs/d}|j||jjƒjddƒƒdS)z+ By default, the project is the board name zHyperspatial express routeÚprojectN)r'r Úto_taskwarriorÚget)r!Zexpected_projectr%r%r&Útest_to_taskwarrior__project's z,TestTrelloIssue.test_to_taskwarrior__project)Ú__name__Ú __module__Ú __qualname__rrr)r-r%r%)r$r&r s  r cs{eZdZidd6dd6Zidd6dd6idd6gd 6d d 6d d 6dd6dd6Zidd6dd6idd6gd 6Zidd6dd6gd 6Zidd6dd6Zidd6dd6Zidd6idd6d6id d6d!6Z idd6id"d6d6idd6d!6Z ‡fd#d$†Z e j d%d&„ƒZe j d'd(„ƒZe j d)d*„ƒZe j d+d,„ƒZe j d-d.„ƒZe j d/d0„ƒZe j d1d2„ƒZe j d3d4„ƒZe j d5d6„ƒZe j d7d8„ƒZe j d9d:„ƒZe j d;d<„ƒZd=Zed>ƒd?d@„ƒZed>ƒdAdB„ƒZed>ƒdCdD„ƒZ‡S)EÚTestTrelloServiceÚB04RDr zMy BoardrÚC4RDzCard 1ÚtintinÚusernameÚmembersr rÚabcdrzhttps://trello.com/c/AAaaBBbbrz'https://trello.com/c/AAaBBbb/42-so-longrZkardzCard 2ZmarioZK4rDzCard 3ÚL15TzList 1ZZZZZzList 2Z commentCardÚtypeZPreumsÚtextÚdataZluidgiZ memberCreatorZDeuzcsTtt|ƒjƒtjƒ|_|jjdƒ|jjdƒ|jjdddƒ|jjdddƒtj tj dd|j |j |j gƒtj tj d d|j|jgƒtj tj d did d 6d d6ƒtj tj ddidd 6dd6ƒtj tj dd|jgƒtj tj dd|j|jgƒdS)NÚgeneralÚmytrelloztrello.api_keyZXXXXz trello.tokenZYYYYz.https://api.trello.com/1/lists/L15T/cards/openÚjsonz0https://api.trello.com/1/boards/B04RD/lists/openz#https://api.trello.com/1/boards/F00ÚF00r z Foo Boardrz#https://api.trello.com/1/boards/B4RÚB4Rz Bar Boardz*https://api.trello.com/1/members/me/boardsz+https://api.trello.com/1/cards/C4RD/actions)rr1rÚ configparserÚRawConfigParserÚconfigÚ add_sectionÚsetÚ responsesÚaddÚGETÚCARD1ÚCARD2ÚCARD3ÚLIST1ÚLIST2ÚBOARDÚCOMMENT1ÚCOMMENT2)r!)r$r%r&r@s0       zTestTrelloService.setUpcCss|jjdddƒt|jddƒ}|jƒ}|jt|ƒidd6dd6id d6d d6gƒdS) Nr=ztrello.include_boardszF00, B4Rr<r?r z Foo Boardrr@z Bar Board)rCrErÚ get_boardsr'Úlist)r!ÚserviceÚboardsr%r%r&Útest_get_boards_configZs   z(TestTrelloService.test_get_boards_configcCsAt|jddƒ}|jƒ}|jt|ƒ|jgƒdS)Nr<r=)rrCrQr'rRrN)r!rSrTr%r%r&Útest_get_boards_apibs z%TestTrelloService.test_get_boards_apicCsJt|jddƒ}|jdƒ}|jt|ƒ|j|jgƒdS)Nr<r=r2)rrCÚ get_listsr'rRrLrM)r!rSÚlistsr%r%r&Útest_get_listshsz TestTrelloService.test_get_listscCsZ|jjdddƒt|jddƒ}|jdƒ}|jt|ƒ|jgƒdS)Nr=ztrello.include_listszList 1r<r2)rCrErrWr'rRrL)r!rSrXr%r%r&Útest_get_lists_includensz(TestTrelloService.test_get_lists_includecCsZ|jjdddƒt|jddƒ}|jdƒ}|jt|ƒ|jgƒdS)Nr=ztrello.exclude_listszList 1r<r2)rCrErrWr'rRrM)r!rSrXr%r%r&Útest_get_lists_excludeusz(TestTrelloService.test_get_lists_excludecCsPt|jddƒ}|jdƒ}|jt|ƒ|j|j|jgƒdS)Nr<r=r8)rrCÚ get_cardsr'rRrIrJrK)r!rSÚcardsr%r%r&Útest_get_cards|sz TestTrelloService.test_get_cardscCsZ|jjdddƒt|jddƒ}|jdƒ}|jt|ƒ|jgƒdS)Nr=ztrello.only_if_assignedr4r<r8)rCrErr\r'rRrI)r!rSr]r%r%r&Útest_get_cards_assigned‚sz)TestTrelloService.test_get_cards_assignedcCsv|jjdddƒ|jjdddƒt|jddƒ}|jdƒ}|jt|ƒ|j|jgƒdS)Nr=ztrello.only_if_assignedr4ztrello.also_unassignedÚtruer<r8)rCrErr\r'rRrIrK)r!rSr]r%r%r&Ú"test_get_cards_assigned_unassigned‰s z4TestTrelloService.test_get_cards_assigned_unassignedcCsJt|jddƒ}|jdƒ}|jt|ƒ|j|jgƒdS)Nr<r=r3)rrCÚ get_commentsr'rRrOrP)r!rSÚcommentsr%r%r&Útest_get_comments‘sz#TestTrelloService.test_get_commentscCsGt|jddƒ}|j|jƒ}|jt|ƒddgƒdS)Nr<r=z@luidgi - Preumsz @mario - Deuz)rrCÚ annotationsrIr'rR)r!rSrer%r%r&Útest_annotations—sz"TestTrelloService.test_annotationscCs`|jjdddƒt|jddƒ}|j|jƒ}|jt|ƒdddgƒdS)Nr<Úannotation_linksr`r=zhttps://trello.com/c/AAaaBBbbz@luidgi - Preumsz @mario - Deuz)rCrErrerIr'rR)r!rSrer%r%r&Útest_annotations_with_linkžs z,TestTrelloService.test_annotations_with_linkcCsÓ|jjdddƒ|jjdddƒt|jddƒ}|jƒ}i dd6d d 6d d 6d d 6dd6dd6dd6dd6dd6dd6ddgd6gd6}t|ƒjƒ}|j||ƒdS)Nr=ztrello.include_listszList 1ztrello.only_if_assignedr4r<z0(bw)#1 - Card 1 .. https://trello.com/c/AAaaBBbbÚ descriptionÚMÚpriorityzMy Boardr*Z trelloboardZ trellolistzCard 1Z trellocardr3Z trellocardidr7Ztrelloshortlinkzhttps://trello.com/c/AAaaBBbbZtrelloshorturlz'https://trello.com/c/AAaBBbb/42-so-longZ trellourlz@luidgi - Preumsz @mario - DeuzreÚtags)rCrErÚissuesrÚget_taskwarrior_recordr')r!rSrmÚexpectedÚactualr%r%r&Ú test_issues©s(   zTestTrelloService.test_issuesNzbugwarrior.services.trello.diecCs!tj|jdƒ|jƒdS)Nr=)rÚvalidate_configrCÚassert_not_called)r!Údier%r%r&Útest_validate_configÃsz&TestTrelloService.test_validate_configcCs7|jjddƒtj|jdƒ|jdƒdS)Nr=z trello.tokenz [mytrello] has no 'trello.token')rCÚ remove_optionrrrÚassert_called_with)r!rtr%r%r&Ú!test_valid_config_no_access_tokenÈsz3TestTrelloService.test_valid_config_no_access_tokencCs7|jjddƒtj|jdƒ|jdƒdS)Nr=ztrello.api_keyz"[mytrello] has no 'trello.api_key')rCrvrrrrw)r!rtr%r%r&Útest_valid_config_no_api_keyÎsz.TestTrelloService.test_valid_config_no_api_key)r.r/r0rNrIrJrKrLrMrOrPrrFÚactivaterUrVrYrZr[r^r_rardrfrhrqÚmaxDiffrrurxryr%r%)r$r&r1.sB " %   r1)Ú __future__rrÚfuturerÚinstall_aliasesÚbuiltinsrrAÚmockrrFZbugwarrior.services.trellorrÚbaser r r r1r%r%r%r&Ús    bugwarrior-1.5.1/tests/__pycache__/test_gerrit.cpython-35.pyc0000644000175000017500000000457413050354041026154 0ustar threebeanthreebean00000000000000 Ö¡X. ã@skddlmZddlZddlZddlmZddlmZmZGdd„deeƒZ dS)é)ÚnextN)Ú GerritServiceé)Ú ServiceTestÚAbstractServiceTestcs eZdZddddddiZddd d d d d dddddddidddd igiZ‡fdd†Zdd„Zejdd„ƒZ ‡S)ÚTestGerritIssuezgerrit.base_uriz https://onezgerrit.usernameÚtwozgerrit.passwordZthreeÚprojectÚnovaZ_numberrÚbranchÚmasterÚtopicz test-topicÚsubjectzthis is a titleÚmessagesÚauthorÚusernamez Iam AuthorÚmessagezthis is a messageZ_revision_numbercs)tt|ƒjƒ|jtƒ|_dS)N)ÚsuperrÚsetUpÚget_mock_servicerÚservice)Úself)Ú __class__©ú5/home/threebean/devel/bugwarrior/tests/test_gerrit.pyrszTestGerritIssue.setUpcCs†dgddi}|jj|j|ƒ}|jƒ}dgdddddd d d d dd ddddgi }|j||ƒdS)NÚ annotationsÚurlz this is a urlÚpriorityÚMr r ÚgerritidrÚ gerritsummaryzthis is a titleÚ gerriturlÚ gerritbranchr Ú gerrittopicz test-topicÚtags)rÚget_issue_for_recordÚrecordÚto_taskwarriorÚ assertEqual)rÚextraÚissueÚactualÚexpectedrrrÚtest_to_taskwarrior s   z#TestGerritIssue.test_to_taskwarriorcCsš|jdddtj|jgƒƒt|jjƒƒ}ddgdddd d d d d dddddddddgi }|j|jƒ|ƒdS)NzKhttps://one/a/changes/?q=is:open+is:reviewer&o=MESSAGES&o=DETAILED_ACCOUNTSÚbodyz)]}'rz@Iam Author - is is a messageÚ descriptionz0(bw)PR#1 - this is a title .. https://one/#/c/1/rrr zthis is a titler!zhttps://one/#/c/1/r"r r#z test-topicrrr r r$) Ú add_responseÚjsonÚdumpsr&rrÚissuesr(Úget_taskwarrior_record)rr*r,rrrÚ test_issues8s  zTestGerritIssue.test_issues) Ú__name__Ú __module__Ú __qualname__ÚSERVICE_CONFIGr&rr-Ú responsesÚactivater5rr)rrr s   r) Úbuiltinsrr1r:Zbugwarrior.services.gerritrÚbaserrrrrrrÚs   bugwarrior-1.5.1/tests/__pycache__/test_gitlab.cpython-35.pyc0000664000175000017500000001536013064355425026133 0ustar threebeanthreebean00000000000000 ÑÚÑXã@sÉddlmZejƒddlmZddlZddlZddlZddlZddl m Z ddl m Z ddl mZmZmZGdd „d eƒZGd d „d eeƒZdS) é)Ústandard_library)ÚnextN)Ú ServiceConfig)Ú GitlabServiceé)Ú ConfigTestÚ ServiceTestÚAbstractServiceTestcsReZdZ‡fdd†Zdd„Zdd„Zdd„Zd d „Z‡S) ÚTestGitlabServicecstt|ƒjƒtjƒ|_|jjdƒ|jjdƒ|jjdddƒ|jjdddƒtt j |jdƒ|_ dS)NÚgeneralÚ myservicez gitlab.loginZfoobarz gitlab.tokenZXXXXXX) Úsuperr ÚsetUpÚ configparserÚRawConfigParserÚconfigÚ add_sectionÚsetrrÚ CONFIG_PREFIXÚservice_config)Úself)Ú __class__©ú5/home/threebean/devel/bugwarrior/tests/test_gitlab.pyrszTestGitlabService.setUpcCs |jtj|jƒdƒdS)Nzgitlab://foobar@gitlab.com)Ú assertEqualrÚget_keyring_servicer)rrrrÚ%test_get_keyring_service_default_hostsz7TestGitlabService.test_get_keyring_service_default_hostcCs6|jjdddƒ|jtj|jƒdƒdS)Nr z gitlab.hostzgitlab.example.comz"gitlab://foobar@gitlab.example.com)rrrrrr)rrrrÚ$test_get_keyring_service_custom_host!sz6TestGitlabService.test_get_keyring_service_custom_hostcCsH|jjdddƒt|jddƒ}|j|jddgƒdS)Nr zgitlab.include_reposzbaz, banana/treer z foobar/bazz banana/tree)rrrrÚ include_repos)rÚservicerrrÚ,test_add_default_namespace_to_included_repos'sz>TestGitlabService.test_add_default_namespace_to_included_reposcCsH|jjdddƒt|jddƒ}|j|jddgƒdS)Nr zgitlab.exclude_reposzbaz, banana/treer z foobar/bazz banana/tree)rrrrÚ exclude_repos)rrrrrÚ,test_add_default_namespace_to_excluded_repos,sz>TestGitlabService.test_add_default_namespace_to_excluded_repos)Ú__name__Ú __module__Ú __qualname__rrrr r"rr)rrr s    r cs÷eZdZdZddddddiZejjƒejdd ƒjd e j d d ƒZ ejjƒjd e j d d ƒZ ejj ejjƒejjjƒƒjd e j ƒZd dddddddddddgdd d dddddejƒjƒddddd did!d d"d#d$d%d&d'd(dd)d d*id+d d d#d,d%d-d'd.dd)d d/idd0de jƒd e jƒi Zd1d2d3d3d4d5d6giZ‡fd7d8†Zd9d:„Zd;d<„Zejd=d>„ƒZ‡S)?ÚTestGitlabIssueNz gitlab.hostzgitlab.example.comz gitlab.loginÚarbitrary_loginz gitlab.tokenZarbitrary_tokenÚhoursrÚtzinfoÚ microsecondrÚidé*ÚiidéÚ project_idéÚtitlezAdd user settingsÚ descriptionÚÚlabelsÚfeatureÚ milestonezv1.0Zdue_dateÚstateÚclosedÚ updated_atz2012-07-04T13:42:48ZÚ created_atÚassigneeéÚusernameÚ jack_smithÚemailzjack@example.comÚnamez Jack SmithÚactivez2012-05-23T08:01:01ZÚauthorÚ john_smithzjohn@example.comz John Smithz2012-05-23T08:00:58ZÚopenedÚ issue_urlz>https://gitlab.example.com/arbitrary_username/project/issues/3ÚprojectÚtypeÚissueÚ annotationscs)tt|ƒjƒ|jtƒ|_dS)N)r r&rÚget_mock_servicerr)r)rrrroszTestGitlabIssue.setUpcCs8|jj|j|jƒ}|j|jdƒdƒdS)Nz needs workÚ needs_work)rÚget_issue_for_recordÚarbitrary_issueÚarbitrary_extrarÚ_normalize_label_to_tag)rrHrrrÚtest_normalize_label_to_tagss   z+TestGitlabIssue.test_normalize_label_to_tagc(Cs[d|j_|jj|j|jƒ}d|jdd|jjdgddg|j|jd|jd|j|jd|j |jd |j |jd |j |jd |j |j jd d ƒ|j|jjd d ƒ|j|j|j|jd|j|jdd |jd |jd |jd |jd|jdi}|jƒ}|j||ƒdS)NTrFÚpriorityrIÚtagsr5rEr7rGr1r-r*rr2r6rCr>)rÚimport_labels_as_tagsrLrMrNÚdefault_priorityÚURLÚREPOZSTATEÚTYPEÚTITLEÚNUMBERÚ UPDATED_ATÚarbitrary_updatedÚreplaceÚ CREATED_ATÚarbitrary_createdZDUEDATEÚarbitrary_duedateZ DESCRIPTIONÚ MILESTONEZUPVOTESZ DOWNVOTESZWORK_IN_PROGRESSZAUTHORZASSIGNEEÚto_taskwarriorr)rrHÚexpected_outputÚ actual_outputrrrÚtest_to_taskwarrior{s4             z#TestGitlabIssue.test_to_taskwarriorc*Cs(|jddddddddigƒ|jd d|jgƒ|jd dd d d iddigƒt|jjƒƒ}ddgdddddd d|jddddddddd dd!d"d#d$d%d&d'|jd(|jd)dd*d+d,dd-d.d/dd0gi}|j|j ƒ|ƒdS)1Nz>https://gitlab.example.com/api/v3/projects?per_page=100&page=1Újsonr+rÚpathzarbitrary_username/projectZweb_urlz example.comzGhttps://gitlab.example.com/api/v3/projects/1/issues?per_page=100&page=1zPhttps://gitlab.example.com/api/v3/projects/1/issues/42/notes?per_page=100&page=1rBr=rCÚbodyz Some comment.rIz@john_smith - Some comment.r2z4(bw)Is#3 - Add user settings .. example.com/issues/3Zgitlabassigneer>Z gitlabauthorZgitlabcreatedonZgitlabdescriptionr3ZgitlabdownvotesrZgitlabmilestonezv1.0Z gitlabnumberr.Z gitlabrepoZ gitlabstaterDZ gitlabtitlezAdd user settingsZ gitlabtyperHZgitlabupdatedatZ gitlabduedateZ gitlabupvotesZ gitlaburlzexample.com/issues/3Z gitlabwiprQÚMrFrR) Ú add_responserMrrÚissuesr^r[r_rÚget_taskwarrior_record)rrHÚexpectedrrrÚ test_issuesœsH       zTestGitlabIssue.test_issues)r#r$r%ÚmaxDiffÚSERVICE_CONFIGÚdatetimeÚutcnowÚ timedeltar\ÚpytzÚUTCr^r[ÚcombineÚdateÚtodayÚminÚtimer_Ú isoformatrMrNrrPrdÚ responsesÚactivatermrr)rrr&2sf  "        !r&)ÚfuturerÚinstall_aliasesÚbuiltinsrrrprsr{Úbugwarrior.configrZbugwarrior.services.gitlabrÚbaserrr r r&rrrrÚs     "bugwarrior-1.5.1/tests/__pycache__/test_activecollab.cpython-35.pyc0000644000175000017500000001126213050354041027300 0ustar threebeanthreebean00000000000000 ´@#XÖã@sµddlmZddlmZddlZddlZddlZddlZddlZddlm Z ddl m Z m Z Gdd„deƒZ Gd d „d e e ƒZdS) é)Únext)ÚobjectN)ÚActiveCollabServiceé)Ú ServiceTestÚAbstractServiceTestc@s@eZdZdd„Zdd„Zdd„Zdd„Zd S) ÚFakeActiveCollabLibcCs ||_dS)N)Úarbitrary_issue)Úselfr ©r ú;/home/threebean/devel/bugwarrior/tests/test_activecollab.pyÚ__init__szFakeActiveCollabLib.__init__cCs dd|jd|jiiiS)NZ arbitrary_keyZ assignmentsÚtask_id)r )r r r r Ú get_my_taskssz FakeActiveCollabLib.get_my_taskscCsgS)Nr )r r r r Úget_assignment_labelssz)FakeActiveCollabLib.get_assignment_labelscGsgS)Nr )r Úargsr r r Ú get_commentssz FakeActiveCollabLib.get_commentsN)Ú__name__Ú __module__Ú __qualname__r rrrr r r r rs    rc.sœeZdZddddddddiZejjƒejd d ƒjd ej ƒZ ejjƒejd d ƒjd ej ƒZ ye j d dddƒZWn!ek rÅejdƒ‚YnXdddddde jƒiddddddddddd d!d"de jƒid#d$d%ejƒd&d'd(d)d*d d+dd,d-d.d d/d iZ‡fd0d1†Z‡fd2d3†Zd4d5„Zd6d7„Z‡S)8ÚTestActiveCollabIssueszactivecollab.urlZhellozactivecollab.keyZhowdyzactivecollab.user_idÚ2zactivecollab.projectsz 1:one, 2:twoÚhoursrÚtzinfoéz

Ticket Body

ZmdÚformatÚhtmlzPandoc is not installed.ÚpriorityrÚprojectÚ somethingZdue_onZformatted_dateÚ permalinkzhttp://wherever/ré Ú project_nameÚ project_idÚidéÚtypeÚissueZ created_onÚcreated_by_nameÚTesterÚbodyÚnameÚ AnonymousÚ milestonezSprint 1Úestimated_timeÚ tracked_timeÚlabelZON_HOLDZ assignee_idZlabel_idc sItt|ƒjƒd|_tjdƒ|jtƒ|_WdQRXdS)Nz"pyac.library.activeCollab.call_api) ÚsuperrÚsetUpÚmaxDiffÚmockÚpatchÚget_mock_servicerÚservice)r )Ú __class__r r r2Ms zTestActiveCollabIssues.setUpcs1tt|ƒj||Ž}t|jƒ|_|S)N)r1rr6rr Ú activecollab)r rÚkwargsr7)r8r r r6Ss z'TestActiveCollabIssues.get_mock_servicec%CsOddgi}|jj|j|ƒ}d|jdd|jddd|d|j|jd|j|jd|j|jd |j|jd |j|j |j |jd |j |jd |j |jd |j |jd|j|jd|j|jd|j|jd|j|jd|j|jdi}|jƒ}|j||ƒdS)NÚ annotationsz an annotationrÚduerÚMr r#r"r&r(r*r+r$rr.r/r-r0)r7Úget_issue_for_recordr Úarbitrary_due_onZ PERMALINKZ PROJECT_IDZ PROJECT_NAMEZTYPEZ CREATED_ONÚarbitrary_created_onZCREATED_BY_NAMEZBODYÚNAMEZ FOREIGN_IDZTASK_IDZESTIMATED_TIMEZ TRACKED_TIMEZ MILESTONEZLABELÚto_taskwarriorÚ assertEqual)r Zarbitrary_extrar'Zexpected_outputZ actual_outputr r r Útest_to_taskwarriorYs.      z*TestActiveCollabIssues.test_to_taskwarriorc(Cs³t|jjƒƒ}ddddd|jdddd d dd d d ddddddddddddddgddd|jddddd gi}|j|jƒ|ƒdS)!NZacbodyz Ticket BodyZaccreatedbynamer)Z accreatedonZacestimatedtimerZacidr%ZaclabelZ acmilestonezSprint 1Zacnamer,Z acpermalinkzhttp://wherever/Z acprojectidr!Z acprojectnamerZactaskidZ actrackedtimeZactyper'r;Ú descriptionz)(bw)Is#30 - Anonymous .. http://wherever/r<rr=rÚtags)rr7Úissuesr@r?rCÚget_taskwarrior_record)r r'Úexpectedr r r Ú test_issuesys,   z"TestActiveCollabIssues.test_issues)rrrZSERVICE_CONFIGÚdatetimeÚnowÚ timedeltaÚreplaceÚpytzÚUTCr?r@ÚpypandocÚconvertÚ_bodyÚOSErrorÚunittestÚSkipTestÚ isoformatÚrstripr r2r6rDrJr r )r8r r sJ  " "     r)ÚbuiltinsrrrKrUr4rQrOZ bugwarrior.services.activecollabrÚbaserrrrr r r r Ús     bugwarrior-1.5.1/tests/__pycache__/test_teamlab.cpython-35.pyc0000644000175000017500000000514213050354041026255 0ustar threebeanthreebean00000000000000 ´@#X1 ã@skddlmZddlZddlZddlmZddlmZmZGdd„deeƒZ dS)é)ÚnextN)ÚTeamLabServiceé)Ú ServiceTestÚAbstractServiceTestcs…eZdZddddddddiZd d d d d d diddiZ‡fdd†Zdd„Zejdd„ƒZ ‡S)ÚTestTeamlabIssuezteamlab.hostnameÚ somethingz teamlab.loginZalkjdsfzteamlab.passwordZlkjkljzteamlab.project_nameÚabcdefÚtitleÚHelloÚidé Ú projectOwneréŒÚstatusrc s@tt|ƒjƒtjdƒ|jtƒ|_WdQRXdS)Nz6bugwarrior.services.teamlab.TeamLabClient.authenticate)ÚsuperrÚsetUpÚmockÚpatchÚget_mock_servicerÚservice)Úself)Ú __class__©ú6/home/threebean/devel/bugwarrior/tests/test_teamlab.pyrs zTestTeamlabIssue.setUpc sÌd‰|jj|jƒ}d|jdd|jj|j|jd|j|jd|jˆ|j|jddi}‡fdd †}t j j |d d |ƒ|j ƒ}WdQRX|j ||ƒdS) Nzhttp://galkjsdflkj.com/Úprojectzteamlab.project_nameÚpriorityr r rcsˆS)Nr)Úargs)Ú arbitrary_urlrrÚget_url/sz5TestTeamlabIssue.test_to_taskwarrior..get_urlÚ get_issue_urlÚ side_effect)rÚget_issue_for_recordÚarbitrary_issueÚSERVICE_CONFIGÚdefault_priorityÚTITLEÚ FOREIGN_IDÚURLZPROJECTOWNER_IDrrÚobjectÚto_taskwarriorÚ assertEqual)rÚissueÚexpected_outputrÚ actual_outputr)rrÚtest_to_taskwarrior!s   z$TestTeamlabIssue.test_to_taskwarriorcCs~|jdd|jgƒt|jjƒƒ}ddddddd gd d d d ddddi}|j|jƒ|ƒdS)Nz0http://something/api/1.0/project/task/@self.jsonÚjsonÚ descriptionzR(bw)Is#10 - Hello .. http://something/products/projects/tasks.aspx?prjID=140&id=10rÚMrr ÚtagsZ teamlabidr ZteamlabprojectowneridrZ teamlabtitler Z teamlaburlz=http://something/products/projects/tasks.aspx?prjID=140&id=10)Ú add_responser#rrÚissuesr+Úget_taskwarrior_record)rr,ÚexpectedrrrÚ test_issues7s  zTestTeamlabIssue.test_issues) Ú__name__Ú __module__Ú __qualname__r$r#rr/Ú responsesÚactivater8rr)rrr s     r) Úbuiltinsrrr<Zbugwarrior.services.teamlabrÚbaserrrrrrrÚs   bugwarrior-1.5.1/tests/__pycache__/test_gerrit.cpython-34.pyc0000644000175000017500000000441013010652175026145 0ustar threebeanthreebean00000000000000î ´@#XUã@skddlmZddlZddlZddlmZddlmZmZGdd„deeƒZ dS)é)ÚnextN)Ú GerritServiceé)Ú ServiceTestÚAbstractServiceTestcsŸeZdZidd6dd6dd6Zidd6d d 6d d 6iid d6d6dd6d d6gd6Z‡fdd†Zdd„Zejdd„ƒZ ‡S)ÚTestGerritIssuez https://onezgerrit.base_uriÚtwozgerrit.usernameZthreezgerrit.passwordÚnovaÚprojectrZ_numberzthis is a titleÚsubjectz Iam AuthorÚusernameÚauthorzthis is a messageÚmessageZ_revision_numberÚmessagescs)tt|ƒjƒ|jtƒ|_dS)N)ÚsuperrÚsetUpÚget_mock_servicerÚservice)Úself)Ú __class__©ú5/home/threebean/devel/bugwarrior/tests/test_gerrit.pyrszTestGerritIssue.setUpcCsƒigd6dd6}|jj|j|ƒ}|jƒ}igd6dd6dd6dd 6d d 6dd 6gd 6}|j||ƒdS)NÚ annotationsz this is a urlÚurlÚMÚpriorityr r rÚgerritidzthis is a titleÚ gerritsummaryÚ gerriturlÚtags)rÚget_issue_for_recordÚrecordÚto_taskwarriorÚ assertEqual)rÚextraÚissueÚactualÚexpectedrrrÚtest_to_taskwarriors   z#TestGerritIssue.test_to_taskwarriorcCs–|jdddtj|jgƒƒt|jjƒƒ}idgd6dd6dd 6d d 6d d 6dd6dd6gd6}|j|jƒ|ƒdS)NzKhttps://one/a/changes/?q=is:open+is:reviewer&o=MESSAGES&o=DETAILED_ACCOUNTSÚbodyz)]}'z@Iam Author - is is a messagerz0(bw)PR#1 - this is a title .. https://one/#/c/1/Ú descriptionrrzthis is a titlerzhttps://one/#/c/1/rrrr r r) Ú add_responseÚjsonÚdumpsr!rrÚissuesr#Úget_taskwarrior_record)rr%r'rrrÚ test_issues4s  zTestGerritIssue.test_issues) Ú__name__Ú __module__Ú __qualname__ÚSERVICE_CONFIGr!rr(Ú responsesÚactivater0rr)rrr s   r) Úbuiltinsrr,r5Zbugwarrior.services.gerritrÚbaserrrrrrrÚs   bugwarrior-1.5.1/tests/__pycache__/test_bitbucket.cpython-35.pyc0000644000175000017500000001104413050354041026622 0ustar threebeanthreebean00000000000000 ´@#Xiã@s_ddlmZddlZddlmZddlmZmZGdd„deeƒZdS)é)Úunicode_literalsN)ÚBitbucketServiceé)Ú ServiceTestÚAbstractServiceTestcsˆeZdZddddddiZ‡fdd†Zd d „Zejd d „ƒZd d„Z dd„Z ejdd„ƒZ ‡S)ÚTestBitbucketIssuezbitbucket.loginÚ somethingzbitbucket.usernameZsomenamezbitbucket.passwordzsomething elsecs)tt|ƒjƒ|jtƒ|_dS)N)ÚsuperrÚsetUpÚget_mock_servicerÚservice)Úself)Ú __class__©ú8/home/threebean/devel/bugwarrior/tests/test_bitbucket.pyr szTestBitbucketIssue.setUpc Csºddddddi}ddd d d d gi}|jj||ƒ}d |d d|j|dd |d |j|d|j|d|j|di}|jƒ}|j||ƒdS) NÚpriorityÚtrivialÚidZ100Útitlez Some TitleÚurlzhttp://hello-there.com/ÚprojectZ SomethingÚ annotationsZOne)r Úget_issue_for_recordÚ PRIORITY_MAPÚURLÚ FOREIGN_IDZTITLEÚto_taskwarriorÚ assertEqual)r Úarbitrary_issueÚarbitrary_extraÚissueÚexpected_outputÚ actual_outputrrrÚtest_to_taskwarriors"         z&TestBitbucketIssue.test_to_taskwarriorcCsÈ|jdddddddigiƒ|jdddd d d d d dddiiddigiƒ|jdddddiddigƒ|jdddd ddd d dddiiddigiƒ|jddddddidddiigiƒdd „|jjƒDƒ\}}d!d"gd#dd$d d%dd&d'd(d)d*d+d,gi}|j|jƒ|ƒd!d"gd#dd$dd%d-d&d.d(d)d*d+d,gi}|j|jƒ|ƒdS)/Nz4https://api.bitbucket.org/2.0/repositories/somename/ÚjsonÚvaluesÚ full_namezsomename/somerepoZ has_issuesTzDhttps://api.bitbucket.org/2.0/repositories/somename/somerepo/issues/rzSome BugÚstatusÚopenÚlinksÚhtmlÚhrefz example.comrrzNhttps://api.bitbucket.org/1.0/repositories/somename/somerepo/issues/1/commentsZ author_infoÚusernameZnobodyÚcontentz Some comment.zJhttps://api.bitbucket.org/2.0/repositories/somename/somerepo/pullrequests/z Some FeatureÚstatezThttps://api.bitbucket.org/2.0/repositories/somename/somerepo/pullrequests/1/commentsÚuserÚrawcSsg|] }|‘qSrr)Ú.0Úirrrú ]s z2TestBitbucketIssue.test_issues..rz@nobody - Some comment.Z bitbucketidZbitbuckettitleZ bitbucketurlÚ descriptionz"(bw)Is#1 - Some Bug .. example.comrÚMrZsomerepoÚtagszhttps://bitbucket.org/z1(bw)Is#1 - Some Feature .. https://bitbucket.org/)Ú add_responser ÚissuesrÚget_taskwarrior_record)r r ÚprZexpected_issueZ expected_prrrrÚ test_issues4s^  "    zTestBitbucketIssue.test_issuescCs>dddddii}|j|jjd|fƒdƒdS)NrÚFoobarÚassigneer,ZtintinÚfoo)rr Ú get_owner)r r rrrÚtest_get_ownerwsz!TestBitbucketIssue.test_get_ownercCs5ddddi}|j|jjd|fƒƒdS)Nrr<r=r>)Ú assertIsNoner r?)r r rrrÚtest_get_owner_none~s z&TestBitbucketIssue.test_get_owner_nonec Cs|jddddddddd d d iid d igddiƒ|jddddddddd d d iid digiƒt|jjdƒƒ}ddddddd d d iid d ifddddddd d d iid difg}|j||ƒdS)NzDhttps://api.bitbucket.org/2.0/repositories/somename/somerepo/issues/r$r%rzSome Bugr'r(r)r*r+z example.comrrÚnextzKhttps://api.bitbucket.org/2.0/repositories/somename/somerepo/issues/?page=2zSome Other Bugézsomename/somerepo)r7Úlistr Z fetch_issuesr)r r8ÚexpectedrrrÚtest_fetch_issues_pagination…s6   z/TestBitbucketIssue.test_fetch_issues_pagination) Ú__name__Ú __module__Ú __qualname__ÚSERVICE_CONFIGr r#Ú responsesÚactivater;r@rBrGrr)rrr s   C  r) Ú __future__rrLZbugwarrior.services.bitbucketrÚbaserrrrrrrÚs bugwarrior-1.5.1/tests/__pycache__/base.cpython-34.pyc0000644000175000017500000000746213010652175024536 0ustar threebeanthreebean00000000000000î ´@#XÛ ã@s±ddlmZddlZddlZddlZddlZddlZddlZddl m Z Gdd„deƒZ Gdd„dej ƒZ Gdd „d e ƒZdS) é)ÚobjectN)Úconfigc@s.eZdZdZdd„Zdd„ZdS)ÚAbstractServiceTestzE Ensures that certain test methods are implemented for each service. cCs t‚dS)z Test Service.to_taskwarrior(). N)ÚNotImplementedError)Úself©rú./home/threebean/devel/bugwarrior/tests/base.pyÚtest_to_taskwarriorsz'AbstractServiceTest.test_to_taskwarriorcCs t‚dS)a Test Service.issues(). - When the API is accessed via requests, use the responses library to mock requests. - When the API is accessed via a third party library, substitute a fake implementation class for it. N)r)rrrrÚ test_issuess zAbstractServiceTest.test_issuesN)Ú__name__Ú __module__Ú __qualname__Ú__doc__r r rrrrr s  rc@s.eZdZdZdd„Zdd„ZdS)Ú ConfigTestzU Creates config files, configures the environment, and cleans up afterwards. c Csÿtjjƒ|_tjddƒ|_tjj|jdƒ|_ tjj|jdƒ|_ tj |j ƒt |j dƒ}|j d|j ƒWdQX|jtjd.has_optioncs ˆ||S)Nr)r1r2)r3rrÚ get_optionUsz0ServiceTest.get_mock_service..get_optioncstˆ||ƒƒS)N)Úint)r1r2)r5rrÚget_intXsz-ServiceTest.get_mock_service..get_intÚ side_effect) ÚGENERAL_CONFIGrÚSERVICE_CONFIGÚupdateÚmockÚMockr4ÚgetÚgetint) rÚ service_classr1Úconfig_overridesZgeneral_overridesr4r7rZservice_instancer)r5r3rÚget_mock_serviceBs  zServiceTest.get_mock_servicecKs tjtj|dd|dS)NZmatch_querystringT)Ú responsesÚaddÚGET)ÚurlÚkwargsrrrÚ add_responsedszServiceTest.add_response)r r r r9r:rBÚ staticmethodrHrrrrr+:s   r+)Úbuiltinsrr<r(Úos.pathrrÚunittestrCrrrÚTestCaserr+rrrrÚs      bugwarrior-1.5.1/tests/__pycache__/__init__.cpython-34.pyc0000644000175000017500000000020513010652175025347 0ustar threebeanthreebean00000000000000î ¿(ETã@sdS)N©rrrú2/home/threebean/devel/bugwarrior/tests/__init__.pyÚsbugwarrior-1.5.1/tests/__pycache__/test_taiga.cpython-35.pyc0000644000175000017500000000470513050354041025741 0ustar threebeanthreebean00000000000000 ´@#XÒ ã@s_ddlmZddlZddlmZddlmZmZGdd„deeƒZdS)é)ÚnextN)Ú TaigaServiceé)Ú ServiceTestÚAbstractServiceTestc s|eZdZddddiZddddd d d d d dgiZ‡fdd†Zdd„Zejdd„ƒZ ‡S)ÚTestTaigaIssueztaiga.base_uriz https://oneztaiga.auth_tokenÚtwoÚidiÚprojectéÚrefé(Úsubjectzthis is a titleÚtagsÚ bugwarriorcs)tt|ƒjƒ|jtƒ|_dS)N)ÚsuperrÚsetUpÚget_mock_servicerÚservice)Úself)Ú __class__©ú4/home/threebean/devel/bugwarrior/tests/test_taiga.pyrszTestTaigaIssue.setUpcCsƒdddgddi}|jj|j|ƒ}|jƒ}dgdddddd gd d d d ddi}|j||ƒdS)Nr ZawesomeÚ annotationsÚurlz this is a urlÚpriorityÚMrrÚtaigaidr Ú taigasummaryzthis is a titleÚtaigaurl)rÚget_issue_for_recordÚrecordÚto_taskwarriorÚ assertEqual)rÚextraÚissueÚactualÚexpectedrrrÚtest_to_taskwarriors    z"TestTaigaIssue.test_to_taskwarriorcCs d}|jddd|iƒ|jdj|ƒd|jgƒ|jdj|jdƒddd iƒ|jd j|jdƒdd d d iddigƒt|jjƒƒ}ddgdddddd ddgddddddi}|j|jƒ|ƒdS)Nrzhttps://one/api/v1/users/meÚjsonr zFhttps://one/api/v1/userstories?status__is_closed=false&assigned_to={0}zhttps://one/api/v1/projects/{0}r ZslugÚ somethingz(https://one/api/v1/history/userstory/{0}ÚuserÚusernameZyouÚcommentzBlah blah blah!rz@you - Blah blah blah!Ú descriptionzB(bw)Is#40 - this is a title .. https://one/project/something/us/40rrrrrr rzthis is a titlerz#https://one/project/something/us/40)Ú add_responseÚformatr!rrÚissuesr#Úget_taskwarrior_record)rÚuseridr%r'rrrÚ test_issues3s4       zTestTaigaIssue.test_issues) Ú__name__Ú __module__Ú __qualname__ÚSERVICE_CONFIGr!rr(Ú responsesÚactivater4rr)rrr s    r) Úbuiltinsrr9Zbugwarrior.services.taigarÚbaserrrrrrrÚs bugwarrior-1.5.1/tests/__pycache__/test_activecollab.cpython-34.pyc0000644000175000017500000001137613010652175027312 0ustar threebeanthreebean00000000000000î ´@#XÖã@sµddlmZddlmZddlZddlZddlZddlZddlZddlm Z ddl m Z m Z Gdd„deƒZ Gd d „d e e ƒZdS) é)Únext)ÚobjectN)ÚActiveCollabServiceé)Ú ServiceTestÚAbstractServiceTestc@s@eZdZdd„Zdd„Zdd„Zdd„Zd S) ÚFakeActiveCollabLibcCs ||_dS)N)Úarbitrary_issue)Úselfr ©r ú;/home/threebean/devel/bugwarrior/tests/test_activecollab.pyÚ__init__szFakeActiveCollabLib.__init__cCs#iii|j|jd6d6d6S)NÚtask_idZ assignmentsZ arbitrary_key)r )r r r r Ú get_my_taskss z FakeActiveCollabLib.get_my_taskscCsgS)Nr )r r r r Úget_assignment_labelssz)FakeActiveCollabLib.get_assignment_labelscGsgS)Nr )r Úargsr r r Ú get_commentssz FakeActiveCollabLib.get_commentsN)Ú__name__Ú __module__Ú __qualname__r rrrr r r r rs    rc sµeZdZidd6dd6dd6dd6Zejjƒejd d ƒjd ej ƒZ ejjƒejd d ƒjd ej ƒZ ye j d dddƒZWn!ek rÉejdƒ‚YnXidd6dd6ie jƒd6d6dd6dd6dd6dd6dd6d d!6ie jƒd6d"6d#d$6ejƒd%6d&d'6d(d)6d d*6dd+6d,d-6d d.6d d/6Z‡fd0d1†Z‡fd2d3†Zd4d5„Zd6d7„Z‡S)8ÚTestActiveCollabIssuesZhellozactivecollab.urlZhowdyzactivecollab.keyÚ2zactivecollab.user_idz 1:one, 2:twozactivecollab.projectsÚhoursrÚtzinfoéz

Ticket Body

ZmdÚformatÚhtmlzPandoc is not installed.rÚpriorityÚ somethingÚprojectZformatted_dateZdue_onzhttp://wherever/Ú permalinké rÚ project_nameÚ project_idéÚidÚissueÚtypeZ created_onÚTesterÚcreated_by_nameÚbodyÚ AnonymousÚnamezSprint 1Ú milestoneÚestimated_timeÚ tracked_timeZON_HOLDÚlabelZ assignee_idZlabel_idc sHtt|ƒjƒd|_tjdƒ|jtƒ|_WdQXdS)Nz"pyac.library.activeCollab.call_api) ÚsuperrÚsetUpÚmaxDiffÚmockÚpatchÚget_mock_servicerÚservice)r )Ú __class__r r r2Ms zTestActiveCollabIssues.setUpcs1tt|ƒj||Ž}t|jƒ|_|S)N)r1rr6rr Ú activecollab)r rÚkwargsr7)r8r r r6Ss z'TestActiveCollabIssues.get_mock_servicecCsbidgd6}|jj|j|ƒ}i|jdd6|jd6dd6|dd6|jd|j6|jd|j6|jd |j6|jd |j6|j|j 6|jd |j 6|jd |j 6|jd |j 6|jd|j 6|jd|j6|jd|j6|jd|j6|jd|j6|jd|j6}|jƒ}|j||ƒdS)Nz an annotationÚ annotationsrÚdueÚMrr r#r"r'r)r*r,r%rr.r/r-r0)r7Úget_issue_for_recordr Úarbitrary_due_onZ PERMALINKZ PROJECT_IDZ PROJECT_NAMEZTYPEÚarbitrary_created_onZ CREATED_ONZCREATED_BY_NAMEZBODYÚNAMEZ FOREIGN_IDZTASK_IDZESTIMATED_TIMEZ TRACKED_TIMEZ MILESTONEZLABELÚto_taskwarriorÚ assertEqual)r Zarbitrary_extrar&Zexpected_outputZ actual_outputr r r Útest_to_taskwarriorYs2      z*TestActiveCollabIssues.test_to_taskwarriorcCsÇt|jjƒƒ}idd6dd6|jd6dd6dd 6dd 6d d 6d d6dd6dd6dd6dd6dd6dd6gd6dd6|jd6dd6dd6gd 6}|j|jƒ|ƒdS)!Nz Ticket BodyZacbodyr(ZaccreatedbynameZ accreatedonrZacestimatedtimer$ZacidZaclabelzSprint 1Z acmilestoner+Zacnamezhttp://wherever/Z acpermalinkr!Z acprojectidrZ acprojectnameZactaskidZ actrackedtimer&Zactyper;z)(bw)Is#30 - Anonymous .. http://wherever/Ú descriptionr<r=rrÚtags)rr7Úissuesr@r?rCÚget_taskwarrior_record)r r&Úexpectedr r r Ú test_issuesys.   z"TestActiveCollabIssues.test_issues)rrrZSERVICE_CONFIGÚdatetimeÚnowÚ timedeltaÚreplaceÚpytzÚUTCr?r@ÚpypandocÚconvertÚ_bodyÚOSErrorÚunittestÚSkipTestÚ isoformatÚrstripr r2r6rDrJr r )r8r r sN  " "     r)ÚbuiltinsrrrKrUr4rQrOZ bugwarrior.services.activecollabrÚbaserrrrr r r r Ús     bugwarrior-1.5.1/tests/__pycache__/test_youtrak.cpython-35.pyc0000664000175000017500000000724013064355425026365 0ustar threebeanthreebean00000000000000 ÑÚÑXü ã@s±ddlmZejƒddlmZddlZddlZddlmZddl m Z ddl m Z m Z mZGdd „d e ƒZGd d „d ee ƒZdS) é)Ústandard_library)ÚnextN)Ú ServiceConfig)ÚYoutrackServiceé)Ú ConfigTestÚ ServiceTestÚAbstractServiceTestcs.eZdZ‡fdd†Zdd„Z‡S)ÚTestYoutrackServicecsrtt|ƒjƒtjƒ|_|jjdƒ|jjdƒ|jjdddƒ|jjdddƒdS)NÚgeneralÚ myservicezyoutrack.loginÚfoobarzyoutrack.passwordÚXXXXXX)Úsuperr ÚsetUpÚ configparserÚRawConfigParserÚconfigÚ add_sectionÚset)Úself)Ú __class__©ú6/home/threebean/devel/bugwarrior/tests/test_youtrak.pyrs zTestYoutrackService.setUpcCsK|jjdddƒttj|jdƒ}|jtj|ƒdƒdS)Nr z youtrack.hostzyoutrack.example.comz&youtrack://foobar@youtrack.example.com)rrrrÚ CONFIG_PREFIXÚ assertEqualÚget_keyring_service)rÚservice_configrrrÚtest_get_keyring_services  z,TestYoutrackService.test_get_keyring_service)Ú__name__Ú __module__Ú __qualname__rrrr)rrr s r c sÄeZdZdZdddddddd iZd d d d dddid dddid dddigdddiddigiZiZ‡fdd†Zdd„Ze j dd„ƒZ ‡S)ÚTestYoutrackIssueNz youtrack.hostzyoutrack.example.comzyoutrack.loginÚarbitrary_loginzyoutrack.passwordÚarbitrary_passwordzyoutrack.anonymousTÚidzTEST-1ÚfieldÚnameZprojectShortNameÚvalueÚTESTZnumberInProjectÚ1Úsummaryz Hello WorldÚtagÚbugz New Featurecs)tt|ƒjƒ|jtƒ|_dS)N)rr"rÚget_mock_servicerÚservice)r)rrrrBszTestYoutrackIssue.setUpcCs˜d|j_|jj|j|jƒ}ddd|jjdddg|jd|jd |jd |j d|j d i}|j ƒ}|j ||ƒdS) NTÚprojectr)ÚpriorityÚtagsr-Ú new_featurezTEST-1z Hello Worldz-https://youtrack.example.com:443/issue/TEST-1r) r/Z import_tagsÚget_issue_for_recordÚarbitrary_issueÚarbitrary_extraÚdefault_priorityZISSUEÚSUMMARYÚURLZPROJECTÚNUMBERÚto_taskwarriorr)rÚissueÚexpected_outputÚ actual_outputrrrÚtest_to_taskwarriorFs        z%TestYoutrackIssue.test_to_taskwarriorcCs–|jddd|jgiƒt|jjƒƒ}ddddd|jjd d d gd d ddddddddi }|j|jƒ|ƒdS)NzQhttps://youtrack.example.com:443/rest/issue?filter=for%3Ame+%23Unresolved&max=100Újsonr<Ú descriptionzL(bw)Is#TEST-1 - Hello World .. https://youtrack.example.com:443/issue/TEST-1r0r)r1r2r-r3Z youtrackissuezTEST-1Zyoutracksummaryz Hello WorldZ youtrackurlz-https://youtrack.example.com:443/issue/TEST-1ZyoutrackprojectZyoutracknumberr)Ú add_responser5rr/Úissuesr7rÚget_taskwarrior_record)rr<ÚexpectedrrrÚ test_issuesXs   zTestYoutrackIssue.test_issues) rr r!ÚmaxDiffÚSERVICE_CONFIGr5r6rr?Ú responsesÚactivaterFrr)rrr"s(       r")ÚfuturerÚinstall_aliasesÚbuiltinsrrrIÚbugwarrior.servicesrZbugwarrior.services.youtrackrÚbaserrr r r"rrrrÚs   bugwarrior-1.5.1/tests/__pycache__/test_string_compat.cpython-34.pyc0000644000175000017500000000223213010652175027522 0ustar threebeanthreebean00000000000000î ´@#X^ã@sUddlZddlZddlmZddlmZGdd„dejƒZdS)éN)Ú MagicMock)ÚIssuec@s"eZdZdZdd„ZdS)ÚStringCompatTestzjThis class implements method to test correct and compatible implementation of __str__ and __repr__ methodscCs°i}idd6dd6dd6gd6}t||ƒ}td|ƒ|_tddƒ|_|jt|ƒtjƒ|j|jƒtjƒ|j tj p¨t |dƒƒd S) zcheck Issue classÚtargetÚprioÚdefault_priorityÚ templatesÚadd_tagsÚ return_valueÚ descriptionÚ __unicode__N) rrÚto_taskwarriorÚget_default_descriptionÚassertIsInstanceÚstrÚsixÚ string_typesÚ__repr__Úassert_ÚPY3Úhasattr)ÚselfÚrecordÚoriginÚissue©rús  bugwarrior-1.5.1/tests/__pycache__/test_redmine.cpython-35.pyc0000644000175000017500000000717713050354041026305 0ustar threebeanthreebean00000000000000 Ö¡XAã@sƒddlmZddlZddlZddlZddlZddlmZddlm Z m Z Gdd„de e ƒZ dS)é)ÚnextN)ÚRedMineServiceé)Ú ServiceTestÚAbstractServiceTestcspeZdZdZddddddiZejjƒjdej j j ƒd d ƒej d ƒZ ejjƒjdej j j ƒd d ƒZ d d dddidd dddide jƒdddddd d ddd dddidd dddidd d dd id!d"d#d ddd$id%e jƒi Z‡fd&d'†Zd(d)„Zejd*d+„ƒZ‡S),ÚTestRedmineIssueNz redmine.urlzhttps://somethingz redmine.keyÚsomething_elsezredmine.issue_limitÚ100ÚtzinfoÚ microsecondrrÚ assigned_toÚidiÚŠÚnamezAdam CoddingtonÚauthorÚ created_onÚdue_onz2016-12-30T16:40:29ZÚ descriptionzThis is a test issue.Z done_ratioi}ÚpriorityéZNormalÚprojectiïjzBoiled Cabbage - YumÚstatusÚNewÚsubjectÚBiscuitsZtrackerÚTaskZ updated_oncs)tt|ƒjƒ|jtƒ|_dS)N)ÚsuperrÚsetUpÚget_mock_servicerÚservice)Úself)Ú __class__©ú6/home/threebean/devel/bugwarrior/tests/test_redmine.pyr:szTestRedmineIssue.setUpc$sLd‰|jj|jƒ}dgd|jƒd|jj|jd|j|jdd|j|jdd|jd|j |jd|j d|j d |j ˆ|j |jd |jd |j|j|j|j|j|jd |jd|jdi}‡fd d†}tjj|dd|ƒ|jƒ}WdQRX|j||ƒdS)Nzhttp://lkjlj.comÚ annotationsrrr rrrrrrr csˆS)Nr!)Úargs)Ú arbitrary_urlr!r"Úget_urlXsz5TestRedmineIssue.test_to_taskwarrior..get_urlÚ get_issue_urlÚ side_effect)rÚget_issue_for_recordÚarbitrary_issueZget_project_nameÚdefault_priorityÚDUEDATEZ ASSIGNED_TOÚAUTHORÚCATEGORYÚ DESCRIPTIONÚESTIMATED_HOURSÚSTATUSÚURLÚSUBJECTZTRACKERÚ CREATED_ONÚarbitrary_createdÚ UPDATED_ONÚarbitrary_updatedÚIDÚ SPENT_HOURSÚ START_DATEÚmockÚpatchÚobjectÚto_taskwarriorÚ assertEqual)rÚissueÚexpected_outputr&Ú actual_outputr!)r%r"Útest_to_taskwarrior>s0           z$TestRedmineIssue.test_to_taskwarriorc(Csô|jddd|jgiƒt|jjƒƒ}dg|jdddddd d d d |jd|jdd ddd|jd|j |jd|j d|j ddddd|j |j |j|jdddgi}|j|jƒ|ƒdS)Nz'https://something/issues.json?limit=100ÚjsonÚissuesr#rz;(bw)Is#363901 - Biscuits .. https://something/issues/363901rÚMrZboiledcabbageyumZ redmineidi}ZredmineassignedtozAdam CoddingtonZ redmineauthorrZredminesubjectrZredminetrackerrZ redmineurlzhttps://something/issues/363901Útags)Ú add_responser*rrrEr,r9r:r.r/r0r1r4r5r6r7r?Úget_taskwarrior_record)rr@Úexpectedr!r!r"Ú test_issues`s4         zTestRedmineIssue.test_issues)Ú__name__Ú __module__Ú __qualname__ÚmaxDiffÚSERVICE_CONFIGÚdatetimeÚutcnowÚreplaceÚdateutilÚtzÚtzutcÚ timedeltar5r7Ú isoformatr*rrCÚ responsesÚactivaterKr!r!)r r"r sH  (        "r) ÚbuiltinsrrQr;rTrYZbugwarrior.services.redminerÚbaserrrr!r!r!r"Ús    bugwarrior-1.5.1/tests/__pycache__/test_db.cpython-35.pyc0000644000175000017500000001166613064557522025263 0ustar threebeanthreebean00000000000000 ÑÚÑX>ã@sddlZddlZddlZddlmZddlmZGdd„dej ƒZ Gdd„deƒZ Gd d „d eƒZ dS) éN)Údbé)Ú ConfigTestc@sdeZdZdd„Zdd„Zdd„Zdd„Zd d „Zd d „Zd d„Z dS)Ú TestMergeLeftcCsddgi|_dS)NÚ annotationsZtesting)Ú issue_dict)Úself©r ú1/home/threebean/devel/bugwarrior/tests/test_db.pyÚsetUp szTestMergeLeft.setUpcKs*tjd||||j||ƒdS)Nr)rÚ merge_leftÚ assertEqual)rÚlocalÚremoteÚkwargsr r r Ú assertMergedszTestMergeLeft.assertMergedcCs|ji|jƒdS)N)rr)rr r r Útest_with_dictszTestMergeLeft.test_with_dictcCs#|jtjjiƒ|jƒdS)N)rÚtaskwÚtaskÚTaskr)rr r r Útest_with_taskwszTestMergeLeft.test_with_taskwcCs|j|j|jƒdS)N)rr)rr r r Útest_already_in_syncsz"TestMergeLeft.test_already_in_synccCsLddgi}tjd|j|ddƒ|jt|jdƒdƒdS)z7 When hamming=False, rough equivalents are duplicated. rz testing ÚhammingFéN)rr rr Úlen)rrr r r Ú!test_rough_equality_hamming_falsesz/TestMergeLeft.test_rough_equality_hamming_falsecCsLddgi}tjd|j|ddƒ|jt|jdƒdƒdS)z: When hamming=True, rough equivalents are not duplicated. rz testing rTrN)rr rr r)rrr r r Ú test_rough_equality_hamming_true$sz.TestMergeLeft.test_rough_equality_hamming_trueN) Ú__name__Ú __module__Ú __qualname__r rrrrrrr r r r r s       rc@seZdZdd„ZdS)ÚTestSynchronizecCsOdd„}tjƒ}|jdƒ|jdddƒ|jdƒ|jdddƒtj|jƒ}|j|jƒdgd giƒd d d d ddddi}xt dƒD]q}t j t |fƒ|dƒ|j||ƒdgd dddd d d ddd d ddddigiƒq·Wd|d .get_tasksÚgeneralÚtargetsÚ my_serviceÚserviceÚgithubÚ completedr!Ú descriptionuBlah blah blah. ☃Z githubtypeÚissueZ githuburlzhttps://example.comÚpriorityÚMrÚstatusÚidrÚurgencyg333333@zYada yada yada.rr"r#Úendr$) Ú ConfigParserÚRawConfigParserÚ add_sectionÚsetrÚ TaskWarriorÚtaskrcr r%ÚrangerÚ synchronizeÚiter)rr(Úconfigr&r0Ú_r'r r r Útest_synchronize.sh   "     z TestSynchronize.test_synchronizeN)rrrrBr r r r r ,s r c@seZdZdd„ZdS)ÚTestUDAscCsºtjƒ}|jdƒ|jdddƒ|jdƒ|jdddƒtttj|dƒƒƒ}|j|dddd d d d d ddddddddddddgƒdS)Nr)r*r+r,r-z uda.githubbody.label=Github Bodyzuda.githubbody.type=stringz(uda.githubcreatedon.label=Github Createdzuda.githubcreatedon.type=datez*uda.githubmilestone.label=Github Milestonezuda.githubmilestone.type=stringz(uda.githubnumber.label=Github Issue/PR #zuda.githubnumber.type=numericz%uda.githubrepo.label=Github Repo Slugzuda.githubrepo.type=stringz"uda.githubtitle.label=Github Titlezuda.githubtitle.type=stringz uda.githubtype.label=Github Typezuda.githubtype.type=stringz(uda.githubupdatedat.label=Github Updatedzuda.githubupdatedat.type=datezuda.githuburl.label=Github URLzuda.githuburl.type=stringz uda.githubuser.label=Github Userzuda.githubuser.type=string) r7r8r9r:ÚsortedÚlistrÚget_defined_udas_as_stringsr )rr@Úudasr r r Ú test_udasƒs6    zTestUDAs.test_udasN)rrrrHr r r r rC‚s rC) ÚunittestÚ configparserr7Ú taskw.taskrÚ bugwarriorrÚbaserÚTestCaserr rCr r r r Ús    Vbugwarrior-1.5.1/tests/__pycache__/test_bts.cpython-34.pyc0000644000175000017500000000617613010652175025454 0ustar threebeanthreebean00000000000000î ´@#X˜ ã@s«ddlmZddlmZddlmZddlZddlmZddlmZm Z Gdd „d eƒZ Gd d „d eƒZ Gd d „d e eƒZ dS)é)Únext)Ústr)ÚobjectN)Úbtsé)Ú ServiceTestÚAbstractServiceTestc@s:eZdZdZdZdZdZdZdZdZ dS)Ú FakeBTSBugi…^ ÚwnppziITP: bugwarrior -- Pull tickets from github, bitbucket, bugzilla, jira, trac, and others into taskwarriorZwishlistÚÚpendingN) Ú__name__Ú __module__Ú __qualname__Úbug_numÚpackageÚsubjectÚseverityÚsourceÚ forwardedr ©rrú2/home/threebean/devel/bugwarrior/tests/test_bts.pyr s r c@s(eZdZdd„Zdd„ZdS)Ú FakeBTSLibcOsdgS)Ni…^ r)ÚselfÚargsÚkwargsrrrÚget_bugsszFakeBTSLib.get_bugscCs|dgkrtgSdS)Ni…^ )r )rrrrrÚ get_statusszFakeBTSLib.get_statusN)r rrrrrrrrrs  rcsTeZdZdZidd6dd6Z‡fdd†Zdd „Zd d „Z‡S) ÚTestBTSServiceNzirl@debian.orgz bts.emailÚ bugwarriorz bts.packagescs,tt|ƒjƒ|jtjƒ|_dS)N)ÚsuperrÚsetUpÚget_mock_servicerZ BTSServiceÚservice)r)Ú __class__rrr!)szTestBTSService.setUpcCsº|jj|jjtƒƒ}i|jtjd6dttjƒ|j6tj |j 6tj|j 6tj |j 6tj|j6tj|j6tj|j6}|jƒ}|j||ƒdS)NÚpriorityzhttps://bugs.debian.org/)r#Úget_issue_for_recordZ_record_for_bugr Ú PRIORITY_MAPrrrÚURLrZSUBJECTÚNUMBERrÚPACKAGErZSOURCErZ FORWARDEDr ZSTATUSÚto_taskwarriorÚ assertEqual)rÚissueÚexpected_outputÚ actual_outputrrrÚtest_to_taskwarrior-s       z"TestBTSService.test_to_taskwarriorc Cs—tjdtƒƒt|jjƒƒ}WdQXi dd6dd6dd6dd 6d d 6dd 6d d6dd6dd6gd6}|j|jƒ|ƒdS)Nz!bugwarrior.services.bts.debianbtsi…^ Z btsnumberr Z btsforwardedr Z btspackageziITP: bugwarrior -- Pull tickets from github, bitbucket, bugzilla, jira, trac, and others into taskwarriorZ btssubjectzhttps://bugs.debian.org/810629ZbtsurlZ btssourcez–(bw)Is#810629 - ITP: bugwarrior -- Pull tickets from github, bitbucket, bugzilla, jira, trac, and others into taskwa .. https://bugs.debian.org/810629Ú descriptionÚLr%r Z btsstatusÚtags)ÚmockÚpatchrrr#Úissuesr,Úget_taskwarrior_record)rr-ÚexpectedrrrÚ test_issuesAs zTestBTSService.test_issues)r rrÚmaxDiffÚSERVICE_CONFIGr!r0r9rr)r$rr s   r) Úbuiltinsrrrr4Úbugwarrior.servicesrÚbaserrr rrrrrrÚs   bugwarrior-1.5.1/tests/__pycache__/test_activecollab2.cpython-34.pyc0000644000175000017500000000633013010652175027366 0ustar threebeanthreebean00000000000000î ´@#X† ã@sƒddlmZddlZddlZddlZddlZddlmZddlm Z m Z Gdd„de e ƒZ dS)é)ÚnextN)ÚActiveCollab2Serviceé)Ú ServiceTestÚAbstractServiceTestcsCeZdZidd6dd6dd6dd6Zejjƒejd d ƒjd ej ƒZ ejjƒejd d ƒjd ej ƒZ i d d6d d6e j ƒd6dd6dd6dd6dd6e j ƒd6dd6dd6dd6iedd 6d!d"6gd#6d$d%6Z ‡fd&d'†Zd(d)„Zejd*d+„ƒZ‡S),ÚTestActiveCollab2Issuez http://hellozactivecollab2.urlÚhowdyzactivecollab2.keyrzactivecollab2.user_idz 1:one, 2:twozactivecollab2.projectsÚhoursrÚtzinfoéÚ somethingÚprojectÚpriorityÚdue_onzhttp://wherever/Ú permalinké Ú ticket_idéÚ project_idÚTicketÚtypeÚ created_onÚ10Ú created_by_idz Ticket BodyÚbodyÚ AnonymousÚnameÚuser_idTZis_ownerZ assigneeszFurther detail.Ú descriptioncs)tt|ƒjƒ|jtƒ|_dS)N)ÚsuperrÚsetUpÚget_mock_servicerÚservice)Úself)Ú __class__©ús    bugwarrior-1.5.1/tests/__pycache__/test_templates.cpython-35.pyc0000644000175000017500000000571213050354041026651 0ustar threebeanthreebean00000000000000 ´@#X ã@s:ddlmZddlmZGdd„deƒZdS)é)ÚIssueé)Ú ServiceTestcsjeZdZ‡fdd†Zdddddd„Zdd„Zdd „Zd d „Zd d „Z‡S)Ú TestTemplatescs5tt|ƒjƒd|_ddddi|_dS)NzConstruct Library on TerminusÚprojectZ end_of_empireÚpriorityÚH)ÚsuperrÚsetUpÚarbitrary_default_descriptionÚarbitrary_issue)Úself)Ú __class__©ú8/home/threebean/devel/bugwarrior/tests/test_templates.pyr s zTestTemplates.setUpNc s‘|dkrin|}ddddddd|ddd |rE|ngi}ti|ƒ}‡‡fd d †|_‡‡fd d †|_|S) NÚannotation_lengthédÚdefault_priorityrÚdescription_lengthÚ templatesÚshortenFÚadd_tagscsˆdkrˆjSˆS)N)r r)Ú descriptionr rrÚsz)TestTemplates.get_issue..csˆdkrˆjSˆS)N)r r)rr rrr s)rÚto_taskwarriorÚget_default_description)r rÚissuerrÚoriginr)rr rÚ get_issueszTestTemplates.get_issuecCsZ|jiƒ}|jƒ}|jjƒ}|jd|jdgiƒ|j||ƒdS)NrÚtags)rÚget_taskwarrior_recordr ÚcopyÚupdater Ú assertEqual)r rÚrecordÚexpected_recordrrrÚtest_default_taskwarrior_record&s   z-TestTemplates.test_default_taskwarrior_recordcCswd}|jd|iƒ}|jƒ}|jjƒ}|jdd|jd|jfdgiƒ|j||ƒdS)Nz"{{ priority }} - {{ description }}rz%s - %srr)rr r r!r"r r#)r Zdescription_templaterr$r%rrrÚtest_override_description2s    z'TestTemplates.test_override_descriptioncCs}d}|jd|iƒ}|jƒ}|jjƒ}|jd|jdd|jdjƒdgiƒ|j||ƒdS)Nzwat_{{ project|upper }}rrzwat_%sr)rr r r!r"r Úupperr#)r Zproject_templaterr$r%rrrÚtest_override_projectEs   z#TestTemplates.test_override_projectcCsp|jdddgƒ}|jƒ}|jjƒ}|jd|jdd|jdgiƒ|j||ƒdS)NrÚonez {{ project }}rrr)rr r r!r"r r#)r rr$r%rrrÚtest_tag_templatesVs  z TestTemplates.test_tag_templates) Ú__name__Ú __module__Ú __qualname__r rr&r'r)r+rr)rrrs    rN)Úbugwarrior.servicesrÚbaserrrrrrÚsbugwarrior-1.5.1/tests/__pycache__/test_bugzilla.cpython-35.pyc0000644000175000017500000000607113050354041026463 0ustar threebeanthreebean00000000000000 ´@#XÑ ã@s•ddlmZddlmZddlZddlmZddlmZddlm Z m Z Gdd „d eƒZ Gd d „d e e ƒZ dS) é)Únext)ÚobjectN)Ú namedtuple)ÚBugzillaServiceé)Ú ServiceTestÚAbstractServiceTestc@s(eZdZdd„Zdd„ZdS)ÚFakeBugzillaLibcCs ||_dS)N)Úrecord)Úselfr ©r ú7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyÚ__init__ szFakeBugzillaLib.__init__cCs.tdt|jjƒƒƒ}||jgS)NÚRecord)rÚlistr Úkeys)r Úqueryrr r r rszFakeBugzillaLib.queryN)Ú__name__Ú __module__Ú __qualname__rrr r r r r s  r c sŽeZdZddddddiZddd d d d d ddddgiZ‡fdd†Z‡fdd†Zdd„Zdd„Z‡S)ÚTestBugzillaServicezbugzilla.base_urizhttp://one.com/zbugzilla.usernameÚhellozbugzilla.passwordZthereÚ componentÚ SomethingÚpriorityZurgentÚstatusÚNEWÚsummaryzThis is the issue summaryÚidi‡ÖÚflagsc s@tt|ƒjƒtjdƒ|jtƒ|_WdQRXdS)Nzbugzilla.Bugzilla)ÚsuperrÚsetUpÚmockÚpatchÚget_mock_servicerÚservice)r )Ú __class__r r r!$szTestBugzillaService.setUpcs1tt|ƒj||Ž}t|jƒ|_|S)N)r rr$r Úarbitrary_recordZbz)r ÚargsÚkwargsr%)r&r r r$)s z$TestBugzillaService.get_mock_servicecCs»ddddgi}|jj|j|ƒ}d|jdd|j|jdd|d|j|jd|j|d|j|jd |j|jd i}|jƒ}|j ||ƒdS) NÚurlzhttp://path/to/issue/Ú annotationsZTwoÚprojectrrrrr) r%Úget_issue_for_recordr'Ú PRIORITY_MAPÚSTATUSÚURLZSUMMARYZBUG_IDÚto_taskwarriorÚ assertEqual)r Úarbitrary_extraÚissueÚexpected_outputÚ actual_outputr r r Útest_to_taskwarrior/s       z'TestBugzillaService.test_to_taskwarriorcCskt|jjƒƒ}dgdddddddd d d d d dddgi }|j|jƒ|ƒdS)Nr+Z bugzillabugidi‡ÖZbugzillastatusrZbugzillasummaryzThis is the issue summaryZ bugzillaurlz/https://http://one.com//show_bug.cgi?id=1234567Ú descriptionz](bw)Is#1234567 - This is the issue summary .. https://http://one.com//show_bug.cgi?id=1234567rÚHr,rÚtags)rr%Úissuesr2Úget_taskwarrior_record)r r4Úexpectedr r r Ú test_issuesJs zTestBugzillaService.test_issues) rrrÚSERVICE_CONFIGr'r!r$r7r>r r )r&r rs    r) Úbuiltinsrrr"Ú collectionsrZbugwarrior.services.bzrÚbaserrr rr r r r Ús  bugwarrior-1.5.1/tests/__pycache__/test_redmine.cpython-34.pyc0000644000175000017500000000534113010652175026300 0ustar threebeanthreebean00000000000000î ´@#Xn ã@skddlmZddlZddlZddlmZddlmZmZGdd„deeƒZ dS)é)ÚnextN)ÚRedMineServiceé)Ú ServiceTestÚAbstractServiceTestcs eZdZidd6dd6dd6Zi idd6d d 6d 6idd6d d 6d 6d d6dd6dd6dd6idd6dd 6d6idd6dd 6d6idd6dd 6d6dd6idd6dd 6d 6d d!6Z‡fd"d#†Zd$d%„Zejd&d'„ƒZ ‡S)(ÚTestRedmineIssuezhttps://somethingz redmine.urlÚsomething_elsez redmine.keyZ 10834u0234zredmine.user_idiÚŠÚidzAdam CoddingtonÚnameÚ assigned_toÚauthorz2014-11-19T16:40:29ZÚ created_onzThis is a test issue.Ú descriptionrZ done_ratioi}éZNormalÚpriorityiïjÚ BugwarriorÚprojectrZNewÚstatusÚBiscuitsÚsubjectÚTaskZtrackerZ updated_oncs)tt|ƒjƒ|jtƒ|_dS)N)ÚsuperrÚsetUpÚget_mock_servicerÚservice)Úself)Ú __class__©ú6/home/threebean/devel/bugwarrior/tests/test_redmine.pyr1szTestRedmineIssue.setUpc sÀd‰|jj|jƒ}i|jddd6|jjd6ˆ|j6|jd|j6|jd|j6}‡fdd†}tjj |d d |ƒ|j ƒ}WdQX|j ||ƒdS) Nzhttp://lkjlj.comrr rrr csˆS)Nr)Úargs)Ú arbitrary_urlrrÚget_urlCsz5TestRedmineIssue.test_to_taskwarrior..get_urlÚ get_issue_urlÚ side_effect) rÚget_issue_for_recordÚarbitrary_issueÚdefault_priorityÚURLÚSUBJECTÚIDÚmockÚpatchÚobjectÚto_taskwarriorÚ assertEqual)rÚissueÚexpected_outputr!Ú actual_outputr)r rÚtest_to_taskwarrior5s  z$TestRedmineIssue.test_to_taskwarriorcCs†|jddi|jgd6ƒt|jjƒƒ}idd6dd6dd 6d d 6d d 6dd6gd6}|j|jƒ|ƒdS)NzAhttps://something/issues.json?assigned_to_id=10834u0234&limit=100ÚjsonÚissuesz;(bw)Is#363901 - Biscuits .. https://something/issues/363901rÚMrrri}Z redmineidrZredminesubjectzhttps://something/issues/363901Z redmineurlÚtags)Ú add_responser%rrr4r.Úget_taskwarrior_record)rr/ÚexpectedrrrÚ test_issuesKs zTestRedmineIssue.test_issues) Ú__name__Ú __module__Ú __qualname__ÚSERVICE_CONFIGr%rr2Ú responsesÚactivater:rr)rrr s@          r) Úbuiltinsrr*r?Zbugwarrior.services.redminerÚbaserrrrrrrÚs   bugwarrior-1.5.1/tests/__pycache__/test_service.cpython-35.pyc0000644000175000017500000000724113061016667026325 0ustar threebeanthreebean00000000000000 ›ÄXJ ã@sjddlZddlmZmZdjddƒZGdd„dejƒZGdd „d ejƒZdS) éN)ÚconfigÚservicesz™Some message that is over 100 characters. This message is so long it's going to fill up your floppy disk taskwarrior backup. Actually it's not that long.Ú ú csFeZdZ‡fdd†Zdd„Zdd„Zdd„Z‡S) ÚTestIssueServicecs6tt|ƒjƒtjƒ|_|jjdƒdS)NÚgeneral)ÚsuperrÚsetUprÚBugwarriorConfigParserÚ add_section)Úself)Ú __class__©ú6/home/threebean/devel/bugwarrior/tests/test_service.pyr szTestIssueService.setUpcCsJtj|jddƒ}|jdtffdƒ}|j|dgƒdS)NrÚtestÚ some_authorz example.comz?@some_author - Some message that is over 100 characters. Thi...)rÚ IssueServicerÚbuild_annotationsÚ LONG_MESSAGEÚ assertEqual)r ÚserviceÚ annotationsrrrÚtest_build_annotations_defaults  z/TestIssueService.test_build_annotations_defaultcCs`|jjdddƒtj|jddƒ}|jdtffdƒ}|j|dgƒdS)NrÚannotation_lengthÚ20rrz example.comz&@some_author - Some message that is...)rÚsetrrrrr)r rrrrrÚtest_build_annotations_limiteds z/TestIssueService.test_build_annotations_limitedcCsl|jjdddƒtj|jddƒ}|jdtffdƒ}|j|djdtƒgƒdS) NrrÚrrz example.comz@some_author - {message}Úmessage)rrrrrrrÚformat)r rrrrrÚ test_build_annotations_limitless#s  z1TestIssueService.test_build_annotations_limitless)Ú__name__Ú __module__Ú __qualname__r rrr rr)r rr s  rcsReZdZ‡fdd†Zdd„Zdd„Zdd„Zd d „Z‡S) Ú TestIssuecs6tt|ƒjƒtjƒ|_|jjdƒdS)Nr)rr$r rr r )r )r rrr .szTestIssue.setUpcCs1tj|jddƒ}tj|_|jdƒS)Nrr)rrrÚIssueÚ ISSUE_CLASSÚget_issue_for_record)r rrrrÚ makeIssue3s zTestIssue.makeIssuecCs/|jƒ}|jtƒ}|j|dƒdS)Nz-(bw)Is# - Some message that is over 100 chara)r(Úbuild_default_descriptionrr)r ÚissueÚ descriptionrrrÚ&test_build_default_description_default8s z0TestIssue.test_build_default_description_defaultcCsE|jjdddƒ|jƒ}|jtƒ}|j|dƒdS)NrÚdescription_lengthrz(bw)Is# - Some message that is)rrr(r)rr)r r*r+rrrÚ&test_build_default_description_limited?s  z0TestIssue.test_build_default_description_limitedcCsQ|jjdddƒ|jƒ}|jtƒ}|j|djdtƒƒdS)Nrr-rz(bw)Is# - {message}r)rrr(r)rrr)r r*r+rrrÚ(test_build_default_description_limitlessGs  z2TestIssue.test_build_default_description_limitless)r!r"r#r r(r,r.r/rr)r rr$-s    r$) ÚunittestÚ bugwarriorrrÚreplacerÚTestCaserr$rrrrÚs "bugwarrior-1.5.1/tests/__pycache__/test_bugzilla.cpython-34.pyc0000644000175000017500000000613313010652175026466 0ustar threebeanthreebean00000000000000î ´@#XÑ ã@s•ddlmZddlmZddlZddlmZddlmZddlm Z m Z Gdd „d eƒZ Gd d „d e e ƒZ dS) é)Únext)ÚobjectN)Ú namedtuple)ÚBugzillaServiceé)Ú ServiceTestÚAbstractServiceTestc@s(eZdZdd„Zdd„ZdS)ÚFakeBugzillaLibcCs ||_dS)N)Úrecord)Úselfr ©r ú7/home/threebean/devel/bugwarrior/tests/test_bugzilla.pyÚ__init__ szFakeBugzillaLib.__init__cCs.tdt|jjƒƒƒ}||jgS)NÚRecord)rÚlistr Úkeys)r Úqueryrr r r rszFakeBugzillaLib.queryN)Ú__name__Ú __module__Ú __qualname__rrr r r r r s  r cs—eZdZidd6dd6dd6Zidd6d d 6d d 6d d6dd6gd6Z‡fdd†Z‡fdd†Zdd„Zdd„Z‡S)ÚTestBugzillaServicezhttp://one.com/zbugzilla.base_uriÚhellozbugzilla.usernameZtherezbugzilla.passwordÚ SomethingÚ componentZurgentÚpriorityÚNEWÚstatuszThis is the issue summaryÚsummaryi‡ÖÚidÚflagsc s?tt|ƒjƒtjdƒ|jtƒ|_WdQXdS)Nzbugzilla.Bugzilla)ÚsuperrÚsetUpÚmockÚpatchÚget_mock_servicerÚservice)r )Ú __class__r r r!$szTestBugzillaService.setUpcs1tt|ƒj||Ž}t|jƒ|_|S)N)r rr$r Úarbitrary_recordZbz)r ÚargsÚkwargsr%)r&r r r$)s z$TestBugzillaService.get_mock_servicecCsÄidd6dgd6}|jj|j|ƒ}i|jdd6|j|jdd6|dd6|jd|j6|d|j6|jd |j6|jd |j6}|jƒ}|j ||ƒdS) Nzhttp://path/to/issue/ÚurlZTwoÚ annotationsrÚprojectrrrr) r%Úget_issue_for_recordr'Ú PRIORITY_MAPÚSTATUSÚURLZSUMMARYZBUG_IDÚto_taskwarriorÚ assertEqual)r Úarbitrary_extraÚissueÚexpected_outputÚ actual_outputr r r Útest_to_taskwarrior/s      z'TestBugzillaService.test_to_taskwarriorcCstt|jjƒƒ}i gd6dd6dd6dd6dd 6d d 6d d 6dd6gd6}|j|jƒ|ƒdS)Nr+i‡ÖZ bugzillabugidrZbugzillastatuszThis is the issue summaryZbugzillasummaryz/https://http://one.com//show_bug.cgi?id=1234567Z bugzillaurlz](bw)Is#1234567 - This is the issue summary .. https://http://one.com//show_bug.cgi?id=1234567Ú descriptionÚHrrr,Útags)rr%Úissuesr2Úget_taskwarrior_record)r r4Úexpectedr r r Ú test_issuesJs zTestBugzillaService.test_issues) rrrÚSERVICE_CONFIGr'r!r$r7r>r r )r&r rs    r) Úbuiltinsrrr"Ú collectionsrZbugwarrior.services.bzrÚbaserrr rr r r r Ús  bugwarrior-1.5.1/tests/__pycache__/test_gitlab.cpython-34.pyc0000644000175000017500000001532213010652175026117 0ustar threebeanthreebean00000000000000î ´@#X~ã@s¹ddlmZejƒddlmZddlZddlZddlZddlZddl m Z ddl m Z m Z mZGdd„de ƒZGd d „d ee ƒZdS) é)Ústandard_library)ÚnextN)Ú GitlabServiceé)Ú ConfigTestÚ ServiceTestÚAbstractServiceTestcsReZdZ‡fdd†Zdd„Zdd„Zdd„Zd d „Z‡S) ÚTestGitlabServicecsrtt|ƒjƒtjƒ|_|jjdƒ|jjdƒ|jjdddƒ|jjdddƒdS)NÚgeneralÚ myservicez gitlab.loginZfoobarz gitlab.tokenZXXXXXX)Úsuperr ÚsetUpÚ configparserÚRawConfigParserÚconfigÚ add_sectionÚset)Úself)Ú __class__©ú5/home/threebean/devel/bugwarrior/tests/test_gitlab.pyr s zTestGitlabService.setUpcCs#|jtj|jdƒdƒdS)Nr zgitlab://foobar@gitlab.com)Ú assertEqualrÚget_keyring_servicer)rrrrÚ%test_get_keyring_service_default_hostsz7TestGitlabService.test_get_keyring_service_default_hostcCs9|jjdddƒ|jtj|jdƒdƒdS)Nr z gitlab.hostzgitlab.example.comz"gitlab://foobar@gitlab.example.com)rrrrr)rrrrÚ$test_get_keyring_service_custom_hostsz6TestGitlabService.test_get_keyring_service_custom_hostcCsH|jjdddƒt|jddƒ}|j|jddgƒdS)Nr zgitlab.include_reposzbaz, banana/treer z foobar/bazz banana/tree)rrrrÚ include_repos)rÚservicerrrÚ,test_add_default_namespace_to_included_repos$sz>TestGitlabService.test_add_default_namespace_to_included_reposcCsH|jjdddƒt|jddƒ}|j|jddgƒdS)Nr zgitlab.exclude_reposzbaz, banana/treer z foobar/bazz banana/tree)rrrrÚ exclude_repos)rrrrrÚ,test_add_default_namespace_to_excluded_repos)sz>TestGitlabService.test_add_default_namespace_to_excluded_repos)Ú__name__Ú __module__Ú __qualname__r rrrrrr)rrr s    r cseZdZdZidd6dd6dd6Zejjƒejdd ƒjd e j d d ƒZ ejjƒjd e j d d ƒZ ejj ejjƒejjjƒƒjd e j ƒZi d d6dd6dd6dd6dd6dgd6id d6dd6dd6ejƒjƒd6dd6dd6dd6d 6id!d6d"d#6d$d%6d&d'6d(d6d)d6d*6id d6d+d#6d,d%6d-d'6d(d6d.d6d/6d0d6e jƒd6e jƒd6Zid1d26d3d36d4d56gd66Z‡fd7d8†Zd9d:„Zd;d<„Zejd=d>„ƒZ‡S)?ÚTestGitlabIssueNzgitlab.example.comz gitlab.hostÚarbitrary_loginz gitlab.loginZarbitrary_tokenz gitlab.tokenÚhoursrÚtzinfoÚ microsecondré*ÚidéÚiidéÚ project_idzAdd user settingsÚtitleÚÚ descriptionÚfeatureÚlabelszv1.0Zdue_dateÚclosedÚstatez2012-07-04T13:42:48ZÚ updated_atÚ created_atÚ milestoneéÚ jack_smithÚusernamezjack@example.comÚemailz Jack SmithÚnameÚactivez2012-05-23T08:01:01ZÚassigneeÚ john_smithzjohn@example.comz John Smithz2012-05-23T08:00:58ZÚauthorÚopenedz>https://gitlab.example.com/arbitrary_username/project/issues/3Ú issue_urlÚprojectÚissueÚtypeÚ annotationscs)tt|ƒjƒ|jtƒ|_dS)N)r r#r Úget_mock_servicerr)r)rrrr lszTestGitlabIssue.setUpcCs8|jj|j|jƒ}|j|jdƒdƒdS)Nz needs workÚ needs_work)rÚget_issue_for_recordÚarbitrary_issueÚarbitrary_extrarÚ_normalize_label_to_tag)rrDrrrÚtest_normalize_label_to_tagps   z+TestGitlabIssue.test_normalize_label_to_tagcCsod|j_|jj|j|jƒ}i|jdd6|jjd6gd6dgd6|jd|j6d|j6|jd|j6|jd |j 6|jd |j 6|jd |j 6|j j d d ƒ|j6|jj d d ƒ|j6|j|j6|jd|j6|jdd |j6d |j6d |j6d |j6d|j6d|j6}|jƒ}|j||ƒdS)NTrCÚpriorityrFr1ÚtagsrBr4rEr.r+r'rr0r7r?r9)rÚimport_labels_as_tagsrIrJrKÚdefault_priorityÚURLÚREPOZSTATEÚTYPEÚTITLEÚNUMBERÚarbitrary_updatedÚreplaceÚ UPDATED_ATÚarbitrary_createdÚ CREATED_ATÚarbitrary_duedateZDUEDATEZ DESCRIPTIONÚ MILESTONEZUPVOTESZ DOWNVOTESZWORK_IN_PROGRESSZAUTHORZASSIGNEEÚto_taskwarriorr)rrDÚexpected_outputÚ actual_outputrrrÚtest_to_taskwarriorxs6             z#TestGitlabIssue.test_to_taskwarriorcCsC|jddidd6dd6dd6gƒ|jd d|jgƒ|jd diid d 6d 6dd6gƒt|jjƒƒ}idgd6dd6dd6d d6|jd6dd6dd6dd6dd6dd 6d!d"6d#d$6d%d&6|jd'6|jd(6dd)6d*d+6dd,6d-d.6dd/6gd06}|j|j ƒ|ƒdS)1Nz>https://gitlab.example.com/api/v3/projects?per_page=100&page=1Újsonrr)zarbitrary_username/projectÚpathz example.comZweb_urlzGhttps://gitlab.example.com/api/v3/projects/1/issues?per_page=100&page=1zPhttps://gitlab.example.com/api/v3/projects/1/issues/42/notes?per_page=100&page=1r?r:r@z Some comment.Úbodyz@john_smith - Some comment.rFz4(bw)Is#3 - Add user settings .. example.com/issues/3r0r9ZgitlabassigneeZ gitlabauthorZgitlabcreatedonr/ZgitlabdescriptionrZgitlabdownvoteszv1.0Zgitlabmilestoner*Z gitlabnumberZ gitlabreporAZ gitlabstatezAdd user settingsZ gitlabtitlerDZ gitlabtypeZgitlabupdatedatZ gitlabduedateZ gitlabupvoteszexample.com/issues/3Z gitlaburlZ gitlabwipÚMrNrCrO) Ú add_responserJrrÚissuesrZrWr\rÚget_taskwarrior_record)rrDÚexpectedrrrÚ test_issues™sL      zTestGitlabIssue.test_issues)r r!r"ÚmaxDiffÚSERVICE_CONFIGÚdatetimeÚutcnowÚ timedeltarXÚpytzÚUTCrZrWÚcombineÚdateÚtodayÚminÚtimer\Ú isoformatrJrKr rMraÚ responsesÚactivaterjrr)rrr#/sj  "         !r#)ÚfuturerÚinstall_aliasesÚbuiltinsrrrmrprxZbugwarrior.services.gitlabrÚbaserrrr r#rrrrÚs      bugwarrior-1.5.1/tests/__pycache__/test_bitbucket.cpython-34.pyc0000644000175000017500000001122013010652175026622 0ustar threebeanthreebean00000000000000î ´@#Xiã@s_ddlmZddlZddlmZddlmZmZGdd„deeƒZdS)é)Úunicode_literalsN)ÚBitbucketServiceé)Ú ServiceTestÚAbstractServiceTestcs‹eZdZidd6dd6dd6Z‡fdd†Zd d „Zejd d „ƒZd d„Z dd„Z ejdd„ƒZ ‡S)ÚTestBitbucketIssueÚ somethingzbitbucket.loginZsomenamezbitbucket.usernamezsomething elsezbitbucket.passwordcs)tt|ƒjƒ|jtƒ|_dS)N)ÚsuperrÚsetUpÚget_mock_servicerÚservice)Úself)Ú __class__©ú8/home/threebean/devel/bugwarrior/tests/test_bitbucket.pyr szTestBitbucketIssue.setUpcCsÆidd6dd6dd6}idd6d d 6d gd 6}|jj||ƒ}i|d d 6|j|dd6|d d 6|d|j6|d|j6|d|j6}|jƒ}|j||ƒdS) NÚtrivialÚpriorityZ100Úidz Some TitleÚtitlezhttp://hello-there.com/ÚurlZ SomethingÚprojectZOneÚ annotations)r Úget_issue_for_recordÚ PRIORITY_MAPÚURLÚ FOREIGN_IDZTITLEÚto_taskwarriorÚ assertEqual)r Úarbitrary_issueÚarbitrary_extraÚissueÚexpected_outputÚ actual_outputrrrÚtest_to_taskwarriors&       z&TestBitbucketIssue.test_to_taskwarriorc Csñ|jddiidd6dd6gd6ƒ|jddiid d 6d d 6iid d6d6d6dd6gd6ƒ|jddiidd6d6dd6gƒ|jddiidd 6d d6iid d6d6d6dd6gd6ƒ|jddiiidd6d6idd6d6gd6ƒdd „|jjƒDƒ\}}id!gd"6dd#6d d$6d d%6d&d'6d(d)6d*d+6gd,6}|j|jƒ|ƒid!gd"6dd#6dd$6d-d%6d.d'6d(d)6d*d+6gd,6}|j|jƒ|ƒdS)/Nz4https://api.bitbucket.org/2.0/repositories/somename/Újsonzsomename/somerepoÚ full_nameTZ has_issuesÚvalueszDhttps://api.bitbucket.org/2.0/repositories/somename/somerepo/issues/zSome BugrÚopenÚstatusz example.comÚhrefÚhtmlÚlinksrrzNhttps://api.bitbucket.org/1.0/repositories/somename/somerepo/issues/1/commentsÚnobodyÚusernameZ author_infoz Some comment.ÚcontentzJhttps://api.bitbucket.org/2.0/repositories/somename/somerepo/pullrequests/z Some FeatureÚstatezThttps://api.bitbucket.org/2.0/repositories/somename/somerepo/pullrequests/1/commentsÚuserÚrawcSsg|] }|‘qSrr)Ú.0Úirrrú ]s z2TestBitbucketIssue.test_issues..z@nobody - Some comment.rZ bitbucketidZbitbuckettitleZ bitbucketurlz"(bw)Is#1 - Some Bug .. example.comÚ descriptionÚMrZsomereporÚtagszhttps://bitbucket.org/z1(bw)Is#1 - Some Feature .. https://bitbucket.org/)Ú add_responser ÚissuesrÚget_taskwarrior_record)r r ÚprZexpected_issueZ expected_prrrrÚ test_issues4sd"    zTestBitbucketIssue.test_issuescCsAidd6idd6d6}|j|jjd|fƒdƒdS)NÚFoobarrZtintinr-ÚassigneeÚfoo)rr Ú get_owner)r r rrrÚtest_get_ownerwsz!TestBitbucketIssue.test_get_ownercCs7idd6dd6}|j|jjd|fƒƒdS)Nr=rr>r?)Ú assertIsNoner r@)r r rrrÚtest_get_owner_none~s z&TestBitbucketIssue.test_get_owner_nonec Cs-|jddiidd6dd6iidd6d 6d 6d d 6gd 6dd6ƒ|jddiidd6dd6iidd6d 6d 6dd 6gd 6ƒt|jjdƒƒ}didd6dd6iidd6d 6d 6d d 6fdidd6dd6iidd6d 6d 6dd 6fg}|j||ƒdS)NzDhttps://api.bitbucket.org/2.0/repositories/somename/somerepo/issues/r$zSome Bugrr'r(z example.comr)r*r+rrr&zKhttps://api.bitbucket.org/2.0/repositories/somename/somerepo/issues/?page=2ÚnextzSome Other Bugézsomename/somerepo)r8Úlistr Z fetch_issuesr)r r9ÚexpectedrrrÚtest_fetch_issues_pagination…s:  z/TestBitbucketIssue.test_fetch_issues_pagination) Ú__name__Ú __module__Ú __qualname__ÚSERVICE_CONFIGr r#Ú responsesÚactivater<rArCrHrr)rrr s   C  r) Ú __future__rrMZbugwarrior.services.bitbucketrÚbaserrrrrrrÚs bugwarrior-1.5.1/tests/__pycache__/test_data.cpython-35.pyc0000644000175000017500000000313413050354041025560 0ustar threebeanthreebean00000000000000 ;®X'ã@s^ddlZddlZddlZddlmZddlmZGdd„deƒZdS)éN)Údataé)Ú ConfigTestcsFeZdZ‡fdd†Zdd„Zdd„Zdd„Z‡S) ÚTestDatacsEtt|ƒjƒtjƒ}|jdƒtj|jƒ|_dS)NÚgeneral) ÚsuperrÚsetUpÚ configparserÚRawConfigParserÚ add_sectionrÚBugwarriorDataÚ lists_path)ÚselfÚconfig)Ú __class__©ú3/home/threebean/devel/bugwarrior/tests/test_data.pyr s  zTestData.setUpcCs<ttj|jjƒjd@ƒ}|j|ddgƒdS)NiÿZ0600Z0o600)ÚoctÚosÚstatrÚdatafileÚst_modeÚassertIn)rZ permissionsrrrÚ assert0600s"zTestData.assert0600c Cs—t|jjdƒ}tjddi|ƒWdQRX|jjddƒ|j|jjdƒdƒ|j|jjƒddddiƒ|j ƒdS)Nzw+ÚoldÚstuffÚkeyÚvalue) ÚopenrrÚjsonÚdumpÚsetÚ assertEqualÚgetÚget_datar)rÚhandlerrrÚ test_get_setszTestData.test_get_setcCs=|jjddƒ|j|jjdƒdƒ|jƒdS)Nrr)rr!r"r#r)rrrrÚtest_set_first_time"szTestData.test_set_first_time)Ú__name__Ú __module__Ú __qualname__rrr&r'rr)rrr s   r)rrr Ú bugwarriorrÚbaserrrrrrÚs   bugwarrior-1.5.1/tests/__pycache__/test_jira.cpython-35.pyc0000664000175000017500000000725713064355425025624 0ustar threebeanthreebean00000000000000 ÏÚÑX` ã@s•ddlmZddlmZddlmZddlZddlmZddlm Z m Z Gdd „d eƒZ Gd d „d e e ƒZ dS) é)Únext)Úobject)Ú namedtupleN)Ú JiraServiceé)Ú ServiceTestÚAbstractServiceTestc@s4eZdZdd„Zdd„Zdd„ZdS)ÚFakeJiraClientcCs ||_dS)N)Úarbitrary_record)Úselfr ©r ú3/home/threebean/devel/bugwarrior/tests/test_jira.pyÚ__init__ szFakeJiraClient.__init__cOs/tdddgƒ}||j|jdƒgS)NÚCaseÚrawÚkey)rr )r ÚargsÚkwargsrr r r Ú search_issuesszFakeJiraClient.search_issuescOsdS)Nr )r rrr r r ÚcommentsszFakeJiraClient.commentsN)Ú__name__Ú __module__Ú __qualname__rrrr r r r r s   r c s¿eZdZddddddiZdZdZd Zd Zd d d dededddddigiddeefiZ‡fdd†Z ‡fdd†Z dd„Z dd„Z ‡S)Ú TestJiraIssuez jira.usernameÚonez jira.base_uriÚtwoz jira.passwordÚthreeiÚ10ÚDONUTÚ lkjaldsfjaldfÚfieldsÚpriorityZBlockerÚsummaryZ timeestimateÚcreatedz2016-06-06T06:07:08.123-0700Z fixVersionsÚnamez1.2.3rz%s-%sc s@tt|ƒjƒtjdƒ|jtƒ|_WdQRXdS)Nzjira.client.JIRA._get_json)ÚsuperrÚsetUpÚmockÚpatchÚget_mock_servicerÚservice)r )Ú __class__r r r&.szTestJiraIssue.setUpcs1tt|ƒj||Ž}t|jƒ|_|S)N)r%rr)r r Újira)r rrr*)r+r r r)3szTestJiraIssue.get_mock_servicecs d‰ddddgi}|jj|j|ƒ}d|jd|j|jddd|dd gd d d d |jˆ|j|jd|j|j|j d|j |j ddi }‡fdd†}t j j|dd|ƒ|jƒ}WdQRX|j||ƒdS)Nz http://oneZ jira_versionéÚ annotationsz an annotationÚprojectr!r ÚtagsÚentryz20160606T060708-0700Újirafixversionz1.2.3ré<csˆS)Nr )r)Ú arbitrary_urlr r Úget_urlTsz2TestJiraIssue.test_to_taskwarrior..get_urlr5Ú side_effect)r*Úget_issue_for_recordr Úarbitrary_projectÚ PRIORITY_MAPÚURLÚ FOREIGN_IDÚSUMMARYÚarbitrary_summaryÚ DESCRIPTIONZESTIMATEÚarbitrary_estimationr'r(rÚto_taskwarriorÚ assertEqual)r Úarbitrary_extraÚissueÚexpected_outputr5Ú actual_outputr )r4r Útest_to_taskwarrior8s*      z!TestJiraIssue.test_to_taskwarriorcCs}t|jjƒƒ}dgddddddddd d d d d ddddddddgi }|j|jƒ|ƒdS)Nr.Ú descriptionz0(bw)Is#10 - lkjaldsfjaldf .. two/browse/DONUT-10r1z20160606T060708-0700ZjiradescriptionZ jiraestimaterr2z1.2.3ZjiraidzDONUT-10Z jirasummaryrZjiraurlztwo/browse/DONUT-10r!ÚHr/rr0)rr*ÚissuesrAÚget_taskwarrior_record)r rCÚexpectedr r r Ú test_issues\s zTestJiraIssue.test_issues) rrrÚSERVICE_CONFIGr?Z arbitrary_idr8r=r r&r)rFrLr r )r+r rs$   $r) ÚbuiltinsrrÚ collectionsrr'Zbugwarrior.services.jirarÚbaserrr rr r r r Ús  bugwarrior-1.5.1/tests/__pycache__/test_string_compat.cpython-35.pyc0000644000175000017500000000222613050354041027521 0ustar threebeanthreebean00000000000000 ´@#X^ã@sUddlZddlZddlmZddlmZGdd„dejƒZdS)éN)Ú MagicMock)ÚIssuec@s"eZdZdZdd„ZdS)ÚStringCompatTestzjThis class implements method to test correct and compatible implementation of __str__ and __repr__ methodscCs¬i}dddddddgi}t||ƒ}td|ƒ|_tddƒ|_|jt|ƒtjƒ|j|jƒtjƒ|j tj p¤t |dƒƒd S) zcheck Issue classÚtargetÚdefault_priorityÚprioÚ templatesÚadd_tagsÚ return_valueÚ descriptionÚ __unicode__N) rrÚto_taskwarriorÚget_default_descriptionÚassertIsInstanceÚstrÚsixÚ string_typesÚ__repr__Úassert_ÚPY3Úhasattr)ÚselfÚrecordÚoriginÚissue©rús  bugwarrior-1.5.1/tests/__pycache__/base.cpython-35.pyc0000644000175000017500000000760313050354041024527 0ustar threebeanthreebean00000000000000 ;®X< ã@sÁddlmZddlZddlZddlZddlZddlZddlZddl m Z ddl m Z Gdd„deƒZ Gdd„dejƒZGd d „d eƒZdS) é)ÚobjectN)Úconfig)ÚBugwarriorDatac@s.eZdZdZdd„Zdd„ZdS)ÚAbstractServiceTestzE Ensures that certain test methods are implemented for each service. cCs t‚dS)z Test Service.to_taskwarrior(). N)ÚNotImplementedError)Úself©rú./home/threebean/devel/bugwarrior/tests/base.pyÚtest_to_taskwarriorsz'AbstractServiceTest.test_to_taskwarriorcCs t‚dS)a Test Service.issues(). - When the API is accessed via requests, use the responses library to mock requests. - When the API is accessed via a third party library, substitute a fake implementation class for it. N)r)rrrr Ú test_issuess zAbstractServiceTest.test_issuesN)Ú__name__Ú __module__Ú __qualname__Ú__doc__r r rrrr rs  rc@s.eZdZdZdd„Zdd„ZdS)Ú ConfigTestzU Creates config files, configures the environment, and cleans up afterwards. c Cstjjƒ|_tjddƒ|_tjj|jdƒ|_ tjj|jdƒ|_ tj |j ƒt |j dƒ}|j d|j ƒWdQRX|jtjd.has_optioncs ˆ||S)Nr)r3r4)r5rr Ú get_optionVsz0ServiceTest.get_mock_service..get_optioncstˆ||ƒƒS)N)Úint)r3r4)r7rr Úget_intYsz-ServiceTest.get_mock_service..get_intÚ side_effect) ÚGENERAL_CONFIGrÚSERVICE_CONFIGÚupdateÚmockÚMockr6ÚgetÚgetintrr"Údata) rÚ service_classr3Úconfig_overridesZgeneral_overridesr6r9rZservice_instancer)r7r5r Úget_mock_serviceCs  zServiceTest.get_mock_servicecKs tjtj|dd|dS)NZmatch_querystringT)Ú responsesÚaddÚGET)ÚurlÚkwargsrrr Ú add_responsefszServiceTest.add_response)r r rr;r<rEÚ staticmethodrKrrrr r-;s  !r-)Úbuiltinsrr>r*Úos.pathrrÚunittestrFrrÚbugwarrior.datarrÚTestCaserr-rrrr Ús      bugwarrior-1.5.1/tests/base.py0000644000175000017500000000620213111574702020244 0ustar threebeanthreebean00000000000000from builtins import object import mock import shutil import os.path import tempfile import unittest import responses from bugwarrior import config from bugwarrior.data import BugwarriorData class AbstractServiceTest(object): """ Ensures that certain test methods are implemented for each service. """ def test_to_taskwarrior(self): """ Test Service.to_taskwarrior(). """ raise NotImplementedError def test_issues(self): """ Test Service.issues(). - When the API is accessed via requests, use the responses library to mock requests. - When the API is accessed via a third party library, substitute a fake implementation class for it. """ raise NotImplementedError class ConfigTest(unittest.TestCase): """ Creates config files, configures the environment, and cleans up afterwards. """ def setUp(self): self.old_environ = os.environ.copy() self.tempdir = tempfile.mkdtemp(prefix='bugwarrior') # Create temporary config files. self.taskrc = os.path.join(self.tempdir, '.taskrc') self.lists_path = os.path.join(self.tempdir, 'lists') os.mkdir(self.lists_path) with open(self.taskrc, 'w+') as fout: fout.write('data.location=%s\n' % self.lists_path) # Configure environment. os.environ['HOME'] = self.tempdir os.environ.pop(config.BUGWARRIORRC, None) os.environ.pop('TASKRC', None) os.environ.pop('XDG_CONFIG_HOME', None) os.environ.pop('XDG_CONFIG_DIRS', None) def tearDown(self): shutil.rmtree(self.tempdir, ignore_errors=True) os.environ = self.old_environ class ServiceTest(ConfigTest): GENERAL_CONFIG = { 'annotation_length': 100, 'description_length': 100, } SERVICE_CONFIG = { } @classmethod def setUpClass(cls): cls.maxDiff = None def get_mock_service( self, service_class, section='unspecified', config_overrides=None, general_overrides=None ): options = { 'general': self.GENERAL_CONFIG.copy(), section: self.SERVICE_CONFIG.copy(), } if config_overrides: options[section].update(config_overrides) if general_overrides: options['general'].update(general_overrides) def has_option(section, name): try: return options[section][name] except KeyError: return False def get_option(section, name): return options[section][name] def get_int(section, name): return int(get_option(section, name)) config = mock.Mock() config.has_option = mock.Mock(side_effect=has_option) config.get = mock.Mock(side_effect=get_option) config.getint = mock.Mock(side_effect=get_int) config.data = BugwarriorData(self.lists_path) service_instance = service_class(config, 'general', section) return service_instance @staticmethod def add_response(url, **kwargs): responses.add(responses.GET, url, match_querystring=True, **kwargs) bugwarrior-1.5.1/tests/test_data.pyc0000644000175000017500000000355513111575063021456 0ustar threebeanthreebean00000000000000ó Âù&Yc@s^ddlZddlZddlZddlmZddlmZdefd„ƒYZdS(iÿÿÿÿN(tdatai(t ConfigTesttTestDatacBs,eZd„Zd„Zd„Zd„ZRS(cCsEtt|ƒjƒtjƒ}|jdƒtj|jƒ|_dS(Ntgeneral( tsuperRtsetUpt configparsertRawConfigParsert add_sectionRtBugwarriorDatat lists_path(tselftconfig((s3/home/threebean/devel/bugwarrior/tests/test_data.pyR s  cCs<ttj|jjƒjd@ƒ}|j|ddgƒdS(Niÿt0600t0o600(tocttoststatRtdatafiletst_modetassertIn(R t permissions((s3/home/threebean/devel/bugwarrior/tests/test_data.pyt assert0600s"cCs™t|jjdƒ}tjidd6|ƒWdQX|jjddƒ|j|jjdƒdƒ|j|jjƒidd6dd6ƒ|j ƒdS(Nsw+tstufftoldtkeytvalue( topenRRtjsontdumptsett assertEqualtgettget_dataR(R thandle((s3/home/threebean/devel/bugwarrior/tests/test_data.pyt test_get_sets!cCs=|jjddƒ|j|jjdƒdƒ|jƒdS(NRR(RRRR R(R ((s3/home/threebean/devel/bugwarrior/tests/test_data.pyttest_set_first_time"s(t__name__t __module__RRR#R$(((s3/home/threebean/devel/bugwarrior/tests/test_data.pyR s   (RRRt bugwarriorRtbaseRR(((s3/home/threebean/devel/bugwarrior/tests/test_data.pyts   bugwarrior-1.5.1/tests/test_config.pyc0000644000175000017500000002232013111575063022001 0ustar threebeanthreebean00000000000000ó Âù&Yc@sÉddlmZddlZddlZddlmZddljZddlm Z de fd„ƒYZ de fd „ƒYZ d efd „ƒYZ d efd „ƒYZ defd„ƒYZdS(iÿÿÿÿ(tunicode_literalsN(tTestCasei(t ConfigTesttTestGetConfigPathcBsGeZd„Zd„Zd„Zd„Zd„Zd„Zd„ZRS(cCsitjj|j|ƒ}tjjtjj|ƒƒsRtjtjj|ƒƒnt|dƒjƒ|S(uX Create an empty file in the temporary directory, return the full path. ua( tostpathtjointtempdirtexiststdirnametmakedirstopentclose(tselfRtfpath((s5/home/threebean/devel/bugwarrior/tests/test_config.pytcreates cCs)|jdƒ}|jtjƒ|ƒdS(uX If it exists, use the file at $XDG_CONFIG_HOME/bugwarrior/bugwarriorrc u.config/bugwarrior/bugwarriorrcN(Rt assertEqualstconfigtget_config_path(R trc((s5/home/threebean/devel/bugwarrior/tests/test_config.pyt test_defaultscCs)|jdƒ}|jtjƒ|ƒdS(u: Falls back on .bugwarriorrc if it exists u .bugwarriorrcN(RRRR(R R((s5/home/threebean/devel/bugwarrior/tests/test_config.pyt test_legacy scCs6|jdƒ|jdƒ}|jtjƒ|ƒdS(uY If both files above exist, the one in $XDG_CONFIG_HOME takes precedence u .bugwarriorrcu.config/bugwarrior/bugwarriorrcN(RRRR(R R((s5/home/threebean/devel/bugwarrior/tests/test_config.pyttest_xdg_first's cCs,|jtjƒtjj|jdƒƒdS(uf If no bugwarriorrc exist anywhere, the path to the prefered one is returned. u.config/bugwarrior/bugwarriorrcN(RRRRRRR(R ((s5/home/threebean/devel/bugwarrior/tests/test_config.pyt test_no_file/s cCsYtjj|jdƒ}|tjd<|jdƒ|jdƒ|jtjƒ|ƒdS(u} If $BUGWARRIORRC is set, it takes precedence over everything else (even if the file doesn't exist). umy-bugwarriorcu BUGWARRIORRCu .bugwarriorrcu.config/bugwarrior/bugwarriorrcN( RRRRtenvironRRRR(R R((s5/home/threebean/devel/bugwarrior/tests/test_config.pyttest_BUGWARRIORRC8s    cCs6dtjd<|jdƒ}|jtjƒ|ƒdS(up If $BUGWARRIORRC is set but emty, it is not used and the default file is used instead. uu BUGWARRIORRCu.config/bugwarrior/bugwarriorrcN(RRRRRR(R R((s5/home/threebean/devel/bugwarrior/tests/test_config.pyttest_BUGWARRIORRC_emptyCs ( t__name__t __module__RRRRRRR(((s5/home/threebean/devel/bugwarrior/tests/test_config.pyR s    tTestGetDataPathcBs5eZd„Zd„Zd„Zd„Zd„ZRS(cCs6tt|ƒjƒtjƒ|_|jjdƒdS(Nugeneral(tsuperRtsetUpt configparsertRawConfigParserRt add_section(R ((s5/home/threebean/devel/bugwarrior/tests/test_config.pyROscCs#|j|tj|jdƒƒdS(Nugeneral(t assertEqualRt get_data_path(R texpected_datapath((s5/home/threebean/devel/bugwarrior/tests/test_config.pytassertDataPathTscCs4tjj|jdƒ}tjd<|j|ƒdS(uX TASKDATA should be respected, even when taskrc's data.location is set. udatauTASKDATAN(RRRRRR&(R tdatapath((s5/home/threebean/devel/bugwarrior/tests/test_config.pyt test_TASKDATAXs#cCs!dtjd<|j|jƒdS(uX When TASKDATA is not set, data.location in taskrc should be respected. uuTASKDATAN(RRR&t lists_path(R ((s5/home/threebean/devel/bugwarrior/tests/test_config.pyttest_taskrc_datalocation_s cCsCt|jdƒWdQXdtjd<|jtjjdƒƒdS(uG When data path is not assigned, use default location. uwNuuTASKDATAu~/.task(R ttaskrcRRR&Rt expanduser(R ((s5/home/threebean/devel/bugwarrior/tests/test_config.pyttest_unassignedfs (RRRR&R(R*R-(((s5/home/threebean/devel/bugwarrior/tests/test_config.pyRMs     tTestOracleEvalcBseZd„ZRS(cCs|jtjdƒdƒdS(Nuecho fööbÃ¥ru fööbÃ¥r(R#Rt oracle_eval(R ((s5/home/threebean/devel/bugwarrior/tests/test_config.pyt test_echous(RRR0(((s5/home/threebean/devel/bugwarrior/tests/test_config.pyR.sstTestBugwarriorConfigParsercBs,eZd„Zd„Zd„Zd„ZRS(cCsetjƒ|_|jjdƒ|jjdddƒ|jjdddƒ|jjdddƒdS(Nugeneralusomeintu4usomenoneuusomecharu somestring(RtBugwarriorConfigParserR"tset(R ((s5/home/threebean/devel/bugwarrior/tests/test_config.pyRzs cCs#|j|jjddƒdƒdS(Nugeneralusomeinti(R#Rtgetint(R ((s5/home/threebean/devel/bugwarrior/tests/test_config.pyt test_getintscCs#|j|jjddƒdƒdS(Nugeneralusomenone(R#RR4tNone(R ((s5/home/threebean/devel/bugwarrior/tests/test_config.pyttest_getint_none„scCs-|jtƒ|jjddƒWdQXdS(Nugeneralusomechar(t assertRaisest ValueErrorRR4(R ((s5/home/threebean/devel/bugwarrior/tests/test_config.pyttest_getint_valueerror‡s(RRRR5R7R:(((s5/home/threebean/devel/bugwarrior/tests/test_config.pyR1ys   tTestServiceConfigcBsGeZd„Zd„Zd„Zd„Zd„Zd„Zd„ZRS(cCs±d|_tjƒ|_|jj|jƒ|jj|jddƒ|jj|jddƒ|jj|jddƒ|jj|jdd ƒtjd |j|jƒ|_dS( Nu someserviceusomeprefix.someintu4usomeprefix.somenoneuusomeprefix.somecharu somestringusomeprefix.someboolutrueu someprefix(ttargetRR2R"R3t ServiceConfigtservice_config(R ((s5/home/threebean/devel/bugwarrior/tests/test_config.pyRs cCs#|j|jj|jdƒƒdS(uY Methods not defined in ServiceConfig should be proxied to configparser. usomeprefix.someintN(t assertTrueR>t has_optionR<(R ((s5/home/threebean/devel/bugwarrior/tests/test_config.pyttest_configparser_proxyšscCs|jd|jkƒdS(Nusomeint(R?R>(R ((s5/home/threebean/devel/bugwarrior/tests/test_config.pyttest__contains__¡scCs |j|jjdƒdƒdS(Nusomeintu4(R#R>tget(R ((s5/home/threebean/devel/bugwarrior/tests/test_config.pyttest_get¤scCs&|j|jjdddƒdƒdS(Nu someoptiontdefaultu somedefault(R#R>RC(R ((s5/home/threebean/devel/bugwarrior/tests/test_config.pyttest_get_default§scCs|j|jjdƒƒdS(Nu someoption(t assertIsNoneR>RC(R ((s5/home/threebean/devel/bugwarrior/tests/test_config.pyttest_get_default_none­scCs)|j|jjddtjƒtƒdS(Nusomebooltto_type(tassertIsR>RCRtasbooltTrue(R ((s5/home/threebean/devel/bugwarrior/tests/test_config.pyttest_get_to_type°s( RRRRARBRDRFRHRM(((s5/home/threebean/devel/bugwarrior/tests/test_config.pyR;Œs     (t __future__RRR tunittestRtbugwarrior.configRtbaseRRRR.R1R;(((s5/home/threebean/devel/bugwarrior/tests/test_config.pyts  @&bugwarrior-1.5.1/tests/test_gerrit.py0000644000175000017500000000475113111574702021674 0ustar threebeanthreebean00000000000000from builtins import next import json import responses from bugwarrior.services.gerrit import GerritService from .base import ServiceTest, AbstractServiceTest class TestGerritIssue(AbstractServiceTest, ServiceTest): SERVICE_CONFIG = { 'gerrit.base_uri': 'https://one.com', 'gerrit.username': 'two', 'gerrit.password': 'three', } record = { 'project': 'nova', '_number': 1, 'branch': 'master', 'topic': 'test-topic', 'subject': 'this is a title', 'messages': [{'author': {'username': 'Iam Author'}, 'message': 'this is a message', '_revision_number': 1}], } def setUp(self): super(TestGerritIssue, self).setUp() responses.add( responses.HEAD, self.SERVICE_CONFIG['gerrit.base_uri'] + '/a/', adding_headers={'www-authenticate': 'digest'}) self.service = self.get_mock_service(GerritService) def test_to_taskwarrior(self): extra = { 'annotations': [ # TODO - test annotations? ], 'url': 'this is a url', } issue = self.service.get_issue_for_record(self.record, extra) actual = issue.to_taskwarrior() expected = { 'annotations': [], 'priority': 'M', 'project': 'nova', 'gerritid': 1, 'gerritsummary': 'this is a title', 'gerriturl': 'this is a url', 'gerritbranch': 'master', 'gerrittopic': 'test-topic', 'tags': [], } self.assertEqual(actual, expected) @responses.activate def test_issues(self): self.add_response( 'https://one.com/a/changes/?q=is:open+is:reviewer&o=MESSAGES&o=DETAILED_ACCOUNTS', # The response has some ")]}'" garbage prefixed. body=")]}'" + json.dumps([self.record])) issue = next(self.service.issues()) expected = { 'annotations': [u'@Iam Author - is is a message'], 'description': u'(bw)PR#1 - this is a title .. https://one.com/#/c/1/', 'gerritid': 1, 'gerritsummary': u'this is a title', 'gerriturl': 'https://one.com/#/c/1/', 'gerritbranch': 'master', 'gerrittopic': 'test-topic', 'priority': 'M', 'project': u'nova', 'tags': []} self.assertEqual(issue.get_taskwarrior_record(), expected) bugwarrior-1.5.1/tests/test_trello.pyc0000644000175000017500000002552413111575063022046 0ustar threebeanthreebean00000000000000ó Âù&Yc@sÔddlmZmZddlmZejƒddlmZddlZddl m Z ddl Z ddl m Z ddlmZmZdd lmZmZd efd „ƒYZd efd „ƒYZdS(iÿÿÿÿ(tunicode_literalstprint_function(tstandard_library(tnextN(tpatch(t ServiceConfig(t TrelloServicet TrelloIssuei(t ConfigTestt ServiceTesttTestTrelloIssuecBsneZidd6dd6dd6dd6dd 6d d 6id d6id d6gd6Zd„Zd„Zd„ZRS(u542bbb6583d705eb05bbe491uidi*uidShortu%So long, and thanks for all the fish!unameuAAaaBBbbu shortLinkuhttps://trello.com/c/AAaaBBbbushortUrlu'https://trello.com/c/AAaBBbb/42-so-longuurlufooubarulabelscCs^tt|ƒjƒtdtdddtƒ}idd6dd6}t|j||ƒ|_dS( Nt inline_linkstdescription_lengthitimport_labels_as_tagsuHyperspatial express routeu boardnameu Somethingulistname(tsuperR tsetUptdicttTrueRtJSONtissue(tselftorigintextra((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyRs    cCs#d}|j||jjƒƒdS(u Test the generated description uJ(bw)#42 - So long, and thanks for all the .. https://trello.com/c/AAaaBBbbN(t assertEqualRtget_default_description(Rt expected_desc((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyttest_default_description"scCs/d}|j||jjƒjddƒƒdS(u+ By default, the project is the board name uHyperspatial express routeuprojectN(RRtto_taskwarriortgettNone(Rtexpected_project((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyttest_to_taskwarrior__project(s (t__name__t __module__RRRR(((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyR s  tTestTrelloServicecBs=eZidd6dd6Zidd6dd6idd6gd6d d 6d d 6d d6dd6Zidd6dd6idd6gd6Zidd6dd6gd6Zidd6dd6Zidd6dd6Zidd6idd6d6idd6d 6Zidd6id!d6d6idd6d 6Z d"„Z e j d#„ƒZ e j d$„ƒZe j d%„ƒZe j d&„ƒZe j d'„ƒZe j d(„ƒZe j d)„ƒZe j d*„ƒZe j d+„ƒZe j d,„ƒZe j d-„ƒZe j d.„ƒZd3Zed/ƒd0„ƒZed/ƒd1„ƒZed/ƒd2„ƒZRS(4uB04RDuiduMy BoardunameuC4RDuCard 1utintinuusernameumembersiuidShortuabcdu shortLinkuhttps://trello.com/c/AAaaBBbbushortUrlu'https://trello.com/c/AAaBBbb/42-so-longuurlukarduCard 2umariouK4rDuCard 3uL15TuList 1uZZZZuList 2u commentCardutypeuPreumsutextudatauluidgiu memberCreatoruDeuzcCsott|ƒjƒtjƒ|_|jjdƒ|jjdƒ|jjdddƒ|jjdddƒtt j |jdƒ|_ t j t jdd|j|j|jgƒt j t jd d|j|jgƒt j t jd did d 6d d6ƒt j t jddidd 6dd6ƒt j t jdd|jgƒt j t jdd|j|jgƒdS(Nugeneralumytrelloutrello.api_keyuXXXXu trello.tokenuYYYYu.https://api.trello.com/1/lists/L15T/cards/opentjsonu0https://api.trello.com/1/boards/B04RD/lists/openu#https://api.trello.com/1/boards/F00uF00uidu Foo Boardunameu#https://api.trello.com/1/boards/B4RuB4Ru Bar Boardu*https://api.trello.com/1/members/me/boardsu+https://api.trello.com/1/cards/C4RD/actions(RR"Rt configparsertRawConfigParsertconfigt add_sectiontsetRRt CONFIG_PREFIXtservice_configt responsestaddtGETtCARD1tCARD2tCARD3tLIST1tLIST2tBOARDtCOMMENT1tCOMMENT2(R((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyRAs4       cCss|jjdddƒt|jddƒ}|jƒ}|jt|ƒidd6dd6id d6d d6gƒdS( Numytrelloutrello.include_boardsuF00, B4RugeneraluF00uidu Foo BoardunameuB4Ru Bar Board(R&R(Rt get_boardsRtlist(Rtservicetboards((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyttest_get_boards_config]s   cCsAt|jddƒ}|jƒ}|jt|ƒ|jgƒdS(Nugeneralumytrello(RR&R6RR7R3(RR8R9((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyttest_get_boards_apies cCsJt|jddƒ}|jdƒ}|jt|ƒ|j|jgƒdS(NugeneralumytrellouB04RD(RR&t get_listsRR7R1R2(RR8tlists((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyttest_get_listskscCsZ|jjdddƒt|jddƒ}|jdƒ}|jt|ƒ|jgƒdS(Numytrelloutrello.include_listsuList 1ugeneraluB04RD(R&R(RR<RR7R1(RR8R=((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyttest_get_lists_includeqscCsZ|jjdddƒt|jddƒ}|jdƒ}|jt|ƒ|jgƒdS(Numytrelloutrello.exclude_listsuList 1ugeneraluB04RD(R&R(RR<RR7R2(RR8R=((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyttest_get_lists_excludexscCsPt|jddƒ}|jdƒ}|jt|ƒ|j|j|jgƒdS(NugeneralumytrellouL15T(RR&t get_cardsRR7R.R/R0(RR8tcards((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyttest_get_cardsscCsZ|jjdddƒt|jddƒ}|jdƒ}|jt|ƒ|jgƒdS(Numytrelloutrello.only_if_assignedutintinugeneraluL15T(R&R(RRARR7R.(RR8RB((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyttest_get_cards_assigned…scCsv|jjdddƒ|jjdddƒt|jddƒ}|jdƒ}|jt|ƒ|j|jgƒdS(Numytrelloutrello.only_if_assignedutintinutrello.also_unassignedutrueugeneraluL15T(R&R(RRARR7R.R0(RR8RB((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyt"test_get_cards_assigned_unassignedŒs cCsJt|jddƒ}|jdƒ}|jt|ƒ|j|jgƒdS(NugeneralumytrellouC4RD(RR&t get_commentsRR7R4R5(RR8tcomments((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyttest_get_comments”scCsGt|jddƒ}|j|jƒ}|jt|ƒddgƒdS(Nugeneralumytrellou@luidgi - Preumsu @mario - Deuz(RR&t annotationsR.RR7(RR8RI((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyttest_annotationsšscCs`|jjdddƒt|jddƒ}|j|jƒ}|jt|ƒdddgƒdS(Nugeneraluannotation_linksutrueumytrellouhttps://trello.com/c/AAaaBBbbu@luidgi - Preumsu @mario - Deuz(R&R(RRIR.RR7(RR8RI((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyttest_annotations_with_link¡s cCsÓ|jjdddƒ|jjdddƒt|jddƒ}|jƒ}i dd6d d 6d d 6d d 6dd6dd6dd6dd6dd6dd6ddgd6gd6}t|ƒjƒ}|j||ƒdS(Numytrelloutrello.include_listsuList 1utrello.only_if_assignedutintinugeneralu0(bw)#1 - Card 1 .. https://trello.com/c/AAaaBBbbu descriptionuMupriorityuMy Boarduprojectu trelloboardu trellolistuCard 1u trellocarduC4RDu trellocardiduabcdutrelloshortlinkuhttps://trello.com/c/AAaaBBbbutrelloshorturlu'https://trello.com/c/AAaBBbb/42-so-longu trellourlu@luidgi - Preumsu @mario - Deuzu annotationsutags(R&R(RtissuesRtget_taskwarrior_recordR(RR8RLtexpectedtactual((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyt test_issues¬s(   ubugwarrior.services.trello.diecCs!tj|jdƒ|jƒdS(Numytrello(Rtvalidate_configR*tassert_not_called(Rtdie((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyttest_validate_configÆscCs7|jjddƒtj|jdƒ|jdƒdS(Numytrellou trello.tokenu [mytrello] has no 'trello.token'(R&t remove_optionRRQR*tassert_called_with(RRS((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyt!test_valid_config_no_access_tokenËscCs7|jjddƒtj|jdƒ|jdƒdS(Numytrelloutrello.api_keyu"[mytrello] has no 'trello.api_key'(R&RURRQR*RV(RRS((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyttest_valid_config_no_api_keyÑsN(R R!R3R.R/R0R1R2R4R5RR+tactivateR:R;R>R?R@RCRDRERHRJRKRPRtmaxDiffRRTRWRX(((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyR"/sB" %    (t __future__RRtfutureRtinstall_aliasestbuiltinsRR$tmockRR+tbugwarrior.configRtbugwarrior.services.trelloRRtbaseRR R R"(((s5/home/threebean/devel/bugwarrior/tests/test_trello.pyts    bugwarrior-1.5.1/tests/test_service.pyc0000644000175000017500000001002213111575063022170 0ustar threebeanthreebean00000000000000ó Âù&Yc@sjddlZddlmZmZdjddƒZdejfd„ƒYZdejfd „ƒYZdS( iÿÿÿÿN(tconfigtservicess™Some message that is over 100 characters. This message is so long it's going to fill up your floppy disk taskwarrior backup. Actually it's not that long.s t tTestIssueServicecBs,eZd„Zd„Zd„Zd„ZRS(cCs6tt|ƒjƒtjƒ|_|jjdƒdS(Ntgeneral(tsuperRtsetUpRtBugwarriorConfigParsert add_section(tself((s6/home/threebean/devel/bugwarrior/tests/test_service.pyR scCsJtj|jddƒ}|jdtffdƒ}|j|dgƒdS(NRttestt some_authors example.comu?@some_author - Some message that is over 100 characters. Thi...(Rt IssueServiceRtbuild_annotationst LONG_MESSAGEt assertEqual(R tservicet annotations((s6/home/threebean/devel/bugwarrior/tests/test_service.pyttest_build_annotations_defaults  cCs`|jjdddƒtj|jddƒ}|jdtffdƒ}|j|dgƒdS(NRtannotation_lengtht20R R s example.comu&@some_author - Some message that is...(RtsetRR R RR(R RR((s6/home/threebean/devel/bugwarrior/tests/test_service.pyttest_build_annotations_limiteds cCsl|jjdddƒtj|jddƒ}|jdtffdƒ}|j|djdtƒgƒdS( NRRtR R s example.comu@some_author - {message}tmessage(RRRR R RRtformat(R RR((s6/home/threebean/devel/bugwarrior/tests/test_service.pyt test_build_annotations_limitless#s  (t__name__t __module__RRRR(((s6/home/threebean/devel/bugwarrior/tests/test_service.pyR s  t TestIssuecBs5eZd„Zd„Zd„Zd„Zd„ZRS(cCs6tt|ƒjƒtjƒ|_|jjdƒdS(NR(RRRRRR(R ((s6/home/threebean/devel/bugwarrior/tests/test_service.pyR.scCs1tj|jddƒ}tj|_|jdƒS(NRR (RR RtIssuet ISSUE_CLASStget_issue_for_recordtNone(R R((s6/home/threebean/devel/bugwarrior/tests/test_service.pyt makeIssue3s cCs/|jƒ}|jtƒ}|j|dƒdS(Nu-(bw)Is# - Some message that is over 100 chara(R"tbuild_default_descriptionRR(R tissuet description((s6/home/threebean/devel/bugwarrior/tests/test_service.pyt&test_build_default_description_default8s cCsE|jjdddƒ|jƒ}|jtƒ}|j|dƒdS(NRtdescription_lengthRu(bw)Is# - Some message that is(RRR"R#RR(R R$R%((s6/home/threebean/devel/bugwarrior/tests/test_service.pyt&test_build_default_description_limited?s  cCsQ|jjdddƒ|jƒ}|jtƒ}|j|djdtƒƒdS(NRR'Ru(bw)Is# - {message}R(RRR"R#RRR(R R$R%((s6/home/threebean/devel/bugwarrior/tests/test_service.pyt(test_build_default_description_limitlessGs  (RRRR"R&R(R)(((s6/home/threebean/devel/bugwarrior/tests/test_service.pyR-s     ( tunittestt bugwarriorRRtreplaceRtTestCaseRR(((s6/home/threebean/devel/bugwarrior/tests/test_service.pyts "bugwarrior-1.5.1/tests/test_gerrit.pyc0000644000175000017500000000565013111575063022037 0ustar threebeanthreebean00000000000000ó Âù&Yc@skddlmZddlZddlZddlmZddlmZmZdeefd„ƒYZ dS(iÿÿÿÿ(tnextN(t GerritServicei(t ServiceTesttAbstractServiceTesttTestGerritIssuecBs–eZidd6dd6dd6Zidd6dd 6d d 6d d 6dd6iidd6d6dd6dd6gd6Zd„Zd„Zejd„ƒZRS(shttps://one.comsgerrit.base_urittwosgerrit.usernametthreesgerrit.passwordtnovatprojectit_numbertmastertbranchs test-topicttopicsthis is a titletsubjects Iam Authortusernametauthorsthis is a messagetmessaget_revision_numbertmessagescCsTtt|ƒjƒtjtj|jdddidd6ƒ|jtƒ|_ dS(Nsgerrit.base_uris/a/tadding_headerstdigestswww-authenticate( tsuperRtsetUpt responsestaddtHEADtSERVICE_CONFIGtget_mock_serviceRtservice(tself((s5/home/threebean/devel/bugwarrior/tests/test_gerrit.pyRs cCs‘igd6dd6}|jj|j|ƒ}|jƒ}i gd6dd6dd6dd 6d d 6dd 6d d6dd6gd6}|j||ƒdS(Nt annotationss this is a urlturltMtpriorityRRitgerritidsthis is a titlet gerritsummaryt gerriturlR t gerritbranchs test-topict gerrittopicttags(Rtget_issue_for_recordtrecordtto_taskwarriort assertEqual(Rtextratissuetactualtexpected((s5/home/threebean/devel/bugwarrior/tests/test_gerrit.pyttest_to_taskwarrior%s    cCs¤|jdddtj|jgƒƒt|jjƒƒ}i dgd6dd6dd 6d d 6d d 6dd6dd6dd6dd6gd6}|j|jƒ|ƒdS(NsOhttps://one.com/a/changes/?q=is:open+is:reviewer&o=MESSAGES&o=DETAILED_ACCOUNTStbodys)]}'u@Iam Author - is is a messageRu4(bw)PR#1 - this is a title .. https://one.com/#/c/1/t descriptioniR"uthis is a titleR#shttps://one.com/#/c/1/R$R R%s test-topicR&R R!unovaRR'( t add_responsetjsontdumpsR)RRtissuesR+tget_taskwarrior_record(RR-R/((s5/home/threebean/devel/bugwarrior/tests/test_gerrit.pyt test_issues=s   ( t__name__t __module__RR)RR0RtactivateR8(((s5/home/threebean/devel/bugwarrior/tests/test_gerrit.pyR s   ( tbuiltinsRR4Rtbugwarrior.services.gerritRtbaseRRR(((s5/home/threebean/devel/bugwarrior/tests/test_gerrit.pyts   bugwarrior-1.5.1/tests/test_templates.pyc0000644000175000017500000000661713111575063022545 0ustar threebeanthreebean00000000000000ó Âù&Yc@s:ddlmZddlmZdefd„ƒYZdS(iÿÿÿÿ(tIssuei(t ServiceTestt TestTemplatescBsJeZd„Zddddd„Zd„Zd„Zd„Zd„ZRS(cCs7tt|ƒjƒd|_idd6dd6|_dS(NsConstruct Library on Terminust end_of_empiretprojecttHtpriority(tsuperRtsetUptarbitrary_default_descriptiontarbitrary_issue(tself((s8/home/threebean/devel/bugwarrior/tests/test_templates.pyRs  cs‘|dkrin|}idd6dd6dd6|d6td6|rJ|ngd6}ti|ƒ}‡‡fd †|_‡‡fd †|_|S( Nidtannotation_lengthRtdefault_prioritytdescription_lengtht templatestshortentadd_tagscsˆdkrˆjSˆS(N(tNoneR ((t descriptionR (s8/home/threebean/devel/bugwarrior/tests/test_templates.pytscsˆdkrˆjSˆS(N(RR ((RR (s8/home/threebean/devel/bugwarrior/tests/test_templates.pyR s(RtFalseRtto_taskwarriortget_default_description(R RtissueRRtorigin((RR s8/home/threebean/devel/bugwarrior/tests/test_templates.pyt get_issuescCs\|jiƒ}|jƒ}|jjƒ}|ji|jd6gd6ƒ|j||ƒdS(NRttags(Rtget_taskwarrior_recordR tcopytupdateR t assertEqual(R Rtrecordtexpected_record((s8/home/threebean/devel/bugwarrior/tests/test_templates.pyttest_default_taskwarrior_record&s    cCszd}|ji|d6ƒ}|jƒ}|jjƒ}|jid|jd|jfd6gd6ƒ|j||ƒdS(Ns"{{ priority }} - {{ description }}Rs%s - %sRR(RRR RRR R(R tdescription_templateRR R!((s8/home/threebean/devel/bugwarrior/tests/test_templates.pyttest_override_description2s      cCsd}|ji|d6ƒ}|jƒ}|jjƒ}|ji|jd6d|jdjƒd6gd6ƒ|j||ƒdS(Nswat_{{ project|upper }}RRswat_%sR(RRR RRR tupperR(R tproject_templateRR R!((s8/home/threebean/devel/bugwarrior/tests/test_templates.pyttest_override_projectEs      cCsr|jdddgƒ}|jƒ}|jjƒ}|ji|jd6d|jdgd6ƒ|j||ƒdS(NRtones {{ project }}RRR(RRR RRR R(R RR R!((s8/home/threebean/devel/bugwarrior/tests/test_templates.pyttest_tag_templatesVs   N( t__name__t __module__RRRR"R$R'R)(((s8/home/threebean/devel/bugwarrior/tests/test_templates.pyRs    N(tbugwarrior.servicesRtbaseRR(((s8/home/threebean/devel/bugwarrior/tests/test_templates.pytsbugwarrior-1.5.1/tests/test_youtrak.py0000644000175000017500000000677413111574702022105 0ustar threebeanthreebean00000000000000from future import standard_library standard_library.install_aliases() from builtins import next import configparser import responses from bugwarrior.services import ServiceConfig from bugwarrior.services.youtrack import YoutrackService from .base import ConfigTest, ServiceTest, AbstractServiceTest class TestYoutrackService(ConfigTest): def setUp(self): super(TestYoutrackService, self).setUp() self.config = configparser.RawConfigParser() self.config.add_section('general') self.config.add_section('myservice') self.config.set('myservice', 'youtrack.login', 'foobar') self.config.set('myservice', 'youtrack.password', 'XXXXXX') def test_get_keyring_service(self): self.config.set('myservice', 'youtrack.host', 'youtrack.example.com') service_config = ServiceConfig( YoutrackService.CONFIG_PREFIX, self.config, 'myservice') self.assertEqual( YoutrackService.get_keyring_service(service_config), 'youtrack://foobar@youtrack.example.com') class TestYoutrackIssue(AbstractServiceTest, ServiceTest): maxDiff = None SERVICE_CONFIG = { 'youtrack.host': 'youtrack.example.com', 'youtrack.login': 'arbitrary_login', 'youtrack.password': 'arbitrary_password', 'youtrack.anonymous': True, } arbitrary_issue = { "id": "TEST-1", "field": [ { "name": "projectShortName", "value": "TEST" }, { "name": "numberInProject", "value": "1" }, { "name": "summary", "value": "Hello World" }, ], "tag": [ { "value": "bug", }, { "value": "New Feature", } ] } arbitrary_extra = { } def setUp(self): super(TestYoutrackIssue, self).setUp() self.service = self.get_mock_service(YoutrackService) def test_to_taskwarrior(self): self.service.import_tags = True issue = self.service.get_issue_for_record(self.arbitrary_issue, self.arbitrary_extra) expected_output = { 'project': 'TEST', 'priority': self.service.default_priority, 'tags': [u'bug', u'new_feature'], issue.ISSUE: 'TEST-1', issue.SUMMARY: 'Hello World', issue.URL: 'https://youtrack.example.com:443/issue/TEST-1', issue.PROJECT: 'TEST', issue.NUMBER: 1, } actual_output = issue.to_taskwarrior() self.assertEqual(actual_output, expected_output) @responses.activate def test_issues(self): self.add_response( 'https://youtrack.example.com:443/rest/issue?filter=for%3Ame+%23Unresolved&max=100', json={'issue': [self.arbitrary_issue]}) issue = next(self.service.issues()) expected = { 'description': u'(bw)Is#TEST-1 - Hello World .. https://youtrack.example.com:443/issue/TEST-1', 'project': 'TEST', 'priority': self.service.default_priority, 'tags': [u'bug', u'new_feature'], 'youtrackissue': 'TEST-1', 'youtracksummary': 'Hello World', 'youtrackurl': 'https://youtrack.example.com:443/issue/TEST-1', 'youtrackproject': 'TEST', 'youtracknumber': 1, } self.assertEqual(issue.get_taskwarrior_record(), expected) bugwarrior-1.5.1/tests/__init__.py0000644000175000017500000000000012421224277021061 0ustar threebeanthreebean00000000000000bugwarrior-1.5.1/tests/test_bitbucket.py0000644000175000017500000001315113111574702022346 0ustar threebeanthreebean00000000000000from __future__ import unicode_literals import responses from bugwarrior.services.bitbucket import BitbucketService from .base import ServiceTest, AbstractServiceTest class TestBitbucketIssue(AbstractServiceTest, ServiceTest): SERVICE_CONFIG = { 'bitbucket.login': 'something', 'bitbucket.username': 'somename', 'bitbucket.password': 'something else', } def setUp(self): super(TestBitbucketIssue, self).setUp() self.service = self.get_mock_service(BitbucketService) def test_to_taskwarrior(self): arbitrary_issue = { 'priority': 'trivial', 'id': '100', 'title': 'Some Title', } arbitrary_extra = { 'url': 'http://hello-there.com/', 'project': 'Something', 'annotations': [ 'One', ] } issue = self.service.get_issue_for_record( arbitrary_issue, arbitrary_extra ) expected_output = { 'project': arbitrary_extra['project'], 'priority': issue.PRIORITY_MAP[arbitrary_issue['priority']], 'annotations': arbitrary_extra['annotations'], issue.URL: arbitrary_extra['url'], issue.FOREIGN_ID: arbitrary_issue['id'], issue.TITLE: arbitrary_issue['title'], } actual_output = issue.to_taskwarrior() self.assertEqual(actual_output, expected_output) @responses.activate def test_issues(self): self.add_response( 'https://api.bitbucket.org/2.0/repositories/somename/', json={'values': [{ 'full_name': 'somename/somerepo', 'has_issues': True }]}) self.add_response( 'https://api.bitbucket.org/2.0/repositories/somename/somerepo/issues/', json={'values': [{ 'title': 'Some Bug', 'status': 'open', 'links': {'html': {'href': 'example.com'}}, 'id': 1 }]}) self.add_response( 'https://api.bitbucket.org/1.0/repositories/somename/somerepo/issues/1/comments', json=[{ 'author_info': {'username': 'nobody'}, 'content': 'Some comment.' }]) self.add_response( 'https://api.bitbucket.org/2.0/repositories/somename/somerepo/pullrequests/', json={'values': [{ 'title': 'Some Feature', 'state': 'open', 'links': {'html': {'href': 'example.com'}}, 'id': 1 }]}) self.add_response( 'https://api.bitbucket.org/2.0/repositories/somename/somerepo/pullrequests/1/comments', json={'values': [{ 'user': {'username': 'nobody'}, 'content': {'raw': 'Some comment.'} }]}) issue, pr = [i for i in self.service.issues()] expected_issue = { 'annotations': [u'@nobody - Some comment.'], 'bitbucketid': 1, 'bitbuckettitle': u'Some Bug', 'bitbucketurl': u'example.com', 'description': u'(bw)Is#1 - Some Bug .. example.com', 'priority': 'M', 'project': u'somerepo', 'tags': []} self.assertEqual(issue.get_taskwarrior_record(), expected_issue) expected_pr = { 'annotations': [u'@nobody - Some comment.'], 'bitbucketid': 1, 'bitbuckettitle': u'Some Feature', 'bitbucketurl': 'https://bitbucket.org/', 'description': u'(bw)Is#1 - Some Feature .. https://bitbucket.org/', 'priority': 'M', 'project': u'somerepo', 'tags': []} self.assertEqual(pr.get_taskwarrior_record(), expected_pr) def test_get_owner(self): issue = { 'title': 'Foobar', 'assignee': {'username': 'tintin'}, } self.assertEqual(self.service.get_owner(('foo', issue)), 'tintin') def test_get_owner_none(self): issue = { 'title': 'Foobar', 'assignee': None, } self.assertIsNone(self.service.get_owner(('foo', issue))) @responses.activate def test_fetch_issues_pagination(self): self.add_response( 'https://api.bitbucket.org/2.0/repositories/somename/somerepo/issues/', json={ 'values': [{ 'title': 'Some Bug', 'status': 'open', 'links': {'html': {'href': 'example.com'}}, 'id': 1 }], 'next': 'https://api.bitbucket.org/2.0/repositories/somename/somerepo/issues/?page=2', }) self.add_response( 'https://api.bitbucket.org/2.0/repositories/somename/somerepo/issues/?page=2', json={ 'values': [{ 'title': 'Some Other Bug', 'status': 'open', 'links': {'html': {'href': 'example.com'}}, 'id': 2 }], }) issues = list(self.service.fetch_issues('somename/somerepo')) expected = [ ('somename/somerepo', { 'title': 'Some Bug', 'status': 'open', 'links': {'html': {'href': 'example.com'}}, 'id': 1 }), ('somename/somerepo', { 'title': 'Some Other Bug', 'status': 'open', 'links': {'html': {'href': 'example.com'}}, 'id': 2 }), ] self.assertEqual(issues, expected) bugwarrior-1.5.1/tests/test_taiga.py0000644000175000017500000000472213111574702021463 0ustar threebeanthreebean00000000000000from builtins import next import responses from bugwarrior.services.taiga import TaigaService from .base import ServiceTest, AbstractServiceTest class TestTaigaIssue(AbstractServiceTest, ServiceTest): SERVICE_CONFIG = { 'taiga.base_uri': 'https://one', 'taiga.auth_token': 'two', } record = { 'id': 400, 'project': 4, 'ref': 40, 'subject': 'this is a title', 'tags': [ 'bugwarrior', ], } def setUp(self): super(TestTaigaIssue, self).setUp() self.service = self.get_mock_service(TaigaService) def test_to_taskwarrior(self): extra = { 'project': 'awesome', 'annotations': [ # TODO - test annotations? ], 'url': 'this is a url', } issue = self.service.get_issue_for_record(self.record, extra) actual = issue.to_taskwarrior() expected = { 'annotations': [], 'priority': 'M', 'project': 'awesome', 'tags': ['bugwarrior'], 'taigaid': 40, 'taigasummary': 'this is a title', 'taigaurl': 'this is a url', } self.assertEqual(actual, expected) @responses.activate def test_issues(self): userid = 1 self.add_response( 'https://one/api/v1/users/me', json={'id': userid}) self.add_response( 'https://one/api/v1/userstories?status__is_closed=false&assigned_to={0}'.format( userid), json=[self.record]) self.add_response( 'https://one/api/v1/projects/{0}'.format(self.record['project']), json={'slug': 'something'}) self.add_response( 'https://one/api/v1/history/userstory/{0}'.format( self.record['id']), json=[{'user': {'username': 'you'}, 'comment': 'Blah blah blah!'}]) issue = next(self.service.issues()) expected = { 'annotations': [u'@you - Blah blah blah!'], 'description': u'(bw)Is#40 - this is a title .. https://one/project/something/us/40', 'priority': 'M', 'project': u'something', 'tags': [u'bugwarrior'], 'taigaid': 40, 'taigasummary': u'this is a title', 'taigaurl': u'https://one/project/something/us/40'} self.assertEqual(issue.get_taskwarrior_record(), expected) bugwarrior-1.5.1/tests/test_github.pyc0000644000175000017500000002742513111575063022031 0ustar threebeanthreebean00000000000000ó Âù&Yc@sÜddlmZddlZddlmZddlmZddlZddlZddl m Z ddl m Z m Z ddlmZmZejjƒejd dƒjd ejd d ƒZejjƒjd ejd d ƒZi d d6dd6dd6dd6dd6idd6d6idd6d6idd6gd6ejƒd6ejƒd 6d!d"6Zid#d$6d%d&6gd'6Zd(eefd)„ƒYZd*eefd+„ƒYZd,efd-„ƒYZd.efd/„ƒYZdS(0iÿÿÿÿ(tnextN(tTestCase(tRawConfigParser(t ServiceConfig(t GithubServicet GithubClienti(t ServiceTesttAbstractServiceTestthoursttzinfot microseconditHallottitles;https://github.com/arbitrary_username/arbitrary_repo/pull/1thtml_urlsGhttps://api.github.com/repos/arbitrary_username/arbitrary_repo/issues/1turli tnumbert Somethingtbodytarbitrary_logintlogintusertalphat milestonetbugfixtnametlabelst created_att updated_ats!arbitrary_username/arbitrary_repotrepotonetprojecttissuettypet annotationstTestGithubIssuecBsVeZd Zidd6dd6dd6Zd„Zd„Zd„Zej d „ƒZ RS( Rs github.logintarbitrary_passwordsgithub.passwordtarbitrary_usernamesgithub.usernamecCs)tt|ƒjƒ|jtƒ|_dS(N(tsuperR"tsetUptget_mock_serviceRtservice(tself((s5/home/threebean/devel/bugwarrior/tests/test_github.pyR&0scCs2|jjttƒ}|j|jdƒdƒdS(Ns needs workt needs_work(R(tget_issue_for_recordtARBITRARY_ISSUEtARBITRARY_EXTRAt assertEqualt_normalize_label_to_tag(R)R((s5/home/threebean/devel/bugwarrior/tests/test_github.pyttest_normalize_label_to_tag4s   cCsüt|j_|jjttƒ}itdd6|jjd6gd6dgd6td|j6td|j6td|j 6td |j 6td |j 6t |j 6t|j6td |j6td d |j6td d|j6}|jƒ}|j||ƒdS(NRtpriorityR!RttagsR RR R RRRRR(tTrueR(timport_labels_as_tagsR+R,R-tdefault_prioritytURLtREPOtTYPEtTITLEtNUMBERtARBITRARY_UPDATEDt UPDATED_ATtARBITRARY_CREATEDt CREATED_ATtBODYt MILESTONEtUSERtto_taskwarriorR.(R)Rtexpected_outputt actual_output((s5/home/threebean/devel/bugwarrior/tests/test_github.pyttest_to_taskwarrior<s*         cCsN|jddidd6idd6d6gƒ|jddid d6id d6d6gƒ|jd dtgƒ|jd dtgƒ|jd diidd6d6dd6gƒt|jjƒƒ}idgd6dd6dd6td6dd6dd6dd6dd 6d!d"6td#6d$d%6d&d'6d(d)6d d*6gd+6}|j|jƒ|ƒdS(,Ns.https://api.github.com/user/repos?per_page=100tjsont some_repoRt some_usernameRtownersBhttps://api.github.com/users/arbitrary_username/repos?per_page=100tarbitrary_repoR$sRhttps://api.github.com/repos/arbitrary_username/arbitrary_repo/issues?per_page=100s/https://api.github.com/user/issues?per_page=100s^https://api.github.com/repos/arbitrary_username/arbitrary_repo/issues/10/comments?per_page=100RRsArbitrary comment.Ru%@arbitrary_login - Arbitrary comment.R!uP(bw)Is#10 - Hallo .. https://github.com/arbitrary_username/arbitrary_repo/pull/1t descriptionu Somethingt githubbodytgithubcreatedonualphatgithubmilestonei t githubnumbers!arbitrary_username/arbitrary_repot githubrepouHallot githubtitleRt githubtypetgithubupdatedatu;https://github.com/arbitrary_username/arbitrary_repo/pull/1t githuburluarbitrary_logint githubusertMR1RR2( t add_responseR,RR(tissuesR=R;R.tget_taskwarrior_record(R)Rtexpected((s5/home/threebean/devel/bugwarrior/tests/test_github.pyt test_issuesWsN    N( t__name__t __module__tNonetmaxDifftSERVICE_CONFIGR&R0REt responsestactivateR[(((s5/home/threebean/devel/bugwarrior/tests/test_github.pyR"(s    tTestGithubIssueQuerycBsbeZdZidd6dd6dd6dd6dd 6dd 6Zd „Zd „Zejd „ƒZ RS(Rs github.loginR#sgithub.passwordR$sgithub.usernamesis:open reviewer:octocats github.querytFalsesgithub.include_user_repossgithub.include_user_issuescCs)tt|ƒjƒ|jtƒ|_dS(N(R%RcR&R'RR((R)((s5/home/threebean/devel/bugwarrior/tests/test_github.pyR&—scCsdS(N((R)((s5/home/threebean/devel/bugwarrior/tests/test_github.pyRE›scCsí|jdditgd6ƒ|jddiidd6d6dd 6gƒt|jjƒƒd }id gd 6d d6dd6td6dd6dd6dd6dd6dd6td6dd6dd 6d!d"6d#d$6gd%6}|j|jƒ|ƒdS(&NsPhttps://api.github.com/search/issues?q=is%3Aopen+reviewer%3Aoctocat&per_page=100RFtitemss^https://api.github.com/repos/arbitrary_username/arbitrary_repo/issues/10/comments?per_page=100RRRsArbitrary comment.Riu%@arbitrary_login - Arbitrary comment.R!uP(bw)Is#10 - Hallo .. https://github.com/arbitrary_username/arbitrary_repo/pull/1RKu SomethingRLRMualphaRNi ROs!arbitrary_username/arbitrary_repoRPuHalloRQRRRRSu;https://github.com/arbitrary_username/arbitrary_repo/pull/1RTuarbitrary_loginRURVR1RJRR2( RWR,tlistR(RXR=R;R.RY(R)RRZ((s5/home/threebean/devel/bugwarrior/tests/test_github.pyR[žs4  N( R\R]R^R_R`R&RERaRbR[(((s5/home/threebean/devel/bugwarrior/tests/test_github.pyRcŒs   tTestGithubServicecBsYeZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z RS( cCs¯tƒ|_t|j_|jjdƒ|jjdƒ|jjdddƒ|jjdddƒ|jjdddƒ|jjdd d ƒttj|jdƒ|_ dS( NtgeneraltmygithubR(tgithubs github.loginttintinsgithub.usernametmilousgithub.passwordt t0ps3cr3t( RtconfigRdt interactivet add_sectiontsetRRt CONFIG_PREFIXtservice_config(R)((s5/home/threebean/devel/bugwarrior/tests/test_github.pyR&Ãs  cCs_|jjddƒ|jjdddƒt|jddƒ}|j|jjjddƒdS(NRisgithub.passwords github.tokens"@oracle:eval:echo 1234567890ABCDEFRht Authorizationstoken 1234567890ABCDEF(Rnt remove_optionRqRR.tclienttsessiontheaders(R)R(((s5/home/threebean/devel/bugwarrior/tests/test_github.pyttest_token_authorization_headerÏs cCs,t|jddƒ}|jd|jƒdS(s@ Check that if github.host is not set, we default to github.com RhRis github.comN(RRnt assertEqualsthost(R)R(((s5/home/threebean/devel/bugwarrior/tests/test_github.pyttest_default_host×scCsB|jjdddƒt|jddƒ}|jd|jƒdS(s< Check that if github.host is set, we use its value as host Ris github.hostsgithub.example.comRhN(RnRqRRzR{(R)R(((s5/home/threebean/devel/bugwarrior/tests/test_github.pyttest_overwrite_hostÜscCs&tj|jƒ}|jd|ƒdS(s& Checks that the keyring service name s github://tintin@github.com/milouN(Rtget_keyring_serviceRsRz(R)tkeyring_service((s5/home/threebean/devel/bugwarrior/tests/test_github.pyttest_keyring_serviceâscCs<|jjdddƒtj|jƒ}|jd|ƒdS(s9 Checks that the keyring key depends on the github host. Ris github.hostsgithub.example.coms(github://tintin@github.example.com/milouN(RnRqRR~RsRz(R)R((s5/home/threebean/devel/bugwarrior/tests/test_github.pyttest_keyring_service_hostçscCs2tddƒ}tj|ƒ}|jd|ƒdS(Nt repos_urlshttps://github.com/foo/barsfoo/bar(tdictRtget_repository_from_issueRz(R)Rt repository((s5/home/threebean/devel/bugwarrior/tests/test_github.pyt)test_get_repository_from_issue_url__issueíscCs2tddƒ}tj|ƒ}|jd|ƒdS(NR‚shttps://github.com/foo/barsfoo/bar(RƒRR„Rz(R)RR…((s5/home/threebean/devel/bugwarrior/tests/test_github.pyt0test_get_repository_from_issue_url__pull_requestòscCs2tddƒ}tj|ƒ}|jd|ƒdS(NR‚shttps://github.acme.biz/foo/barsfoo/bar(RƒRR„Rz(R)RR…((s5/home/threebean/devel/bugwarrior/tests/test_github.pyt1test_get_repository_from_issue__enterprise_github÷s( R\R]R&RyR|R}R€RR†R‡Rˆ(((s5/home/threebean/devel/bugwarrior/tests/test_github.pyRgÁs       tTestGithubClientcBs#eZd„Zd„Zd„ZRS(cCs9idd6}td|ƒ}|j|jdƒdƒdS(Ntxxxxttokens github.coms /some/paths https://api.github.com/some/path(RRzt_api_url(R)tauthRv((s5/home/threebean/devel/bugwarrior/tests/test_github.pyt test_api_urlÿs cCs?idd6}td|ƒ}|j|jdddƒdƒdS(NRŠR‹s github.coms/some/path/{foo}tfootbars$https://api.github.com/some/path/bar(RRzRŒ(R)RRv((s5/home/threebean/devel/bugwarrior/tests/test_github.pyttest_api_url_with_contexts  cCs9idd6}td|ƒ}|j|jdƒdƒdS(s/ Test generating an API URL with a custom host RŠR‹sgithub.example.coms /some/paths+https://github.example.com/api/v3/some/pathN(RRzRŒ(R)RRv((s5/home/threebean/devel/bugwarrior/tests/test_github.pyttest_api_url_with_custom_host s   (R\R]RŽR‘R’(((s5/home/threebean/devel/bugwarrior/tests/test_github.pyR‰ýs  (tbuiltinsRtdatetimetunittestRt configparserRtpytzRatbugwarrior.configRtbugwarrior.services.githubRRtbaseRRtutcnowt timedeltatreplacetUTCR=R;t isoformatR,R-R"RcRgR‰(((s5/home/threebean/devel/bugwarrior/tests/test_github.pyts@   "    d5<bugwarrior-1.5.1/tests/test_bugzilla.py0000644000175000017500000001160513111574702022205 0ustar threebeanthreebean00000000000000from builtins import next from builtins import object import mock from collections import namedtuple import configparser from bugwarrior.services.bz import BugzillaService from .base import ConfigTest, ServiceTest, AbstractServiceTest from bugwarrior.config import ServiceConfig class FakeBugzillaLib(object): def __init__(self, record): self.record = record def query(self, query): Record = namedtuple('Record', list(self.record.keys())) return [Record(**self.record)] class TestBugzillaServiceConfig(ConfigTest): def setUp(self): super(TestBugzillaServiceConfig, self).setUp() self.config = configparser.RawConfigParser() self.config.add_section('general') self.config.add_section('mybz') self.service_config = ServiceConfig( BugzillaService.CONFIG_PREFIX, self.config, 'mybz') @mock.patch('bugwarrior.services.bz.die') def test_validate_config_username_password(self, die): self.config.set('mybz', 'bugzilla.base_uri', 'http://one.com/') self.config.set('mybz', 'bugzilla.username', 'me') self.config.set('mybz', 'bugzilla.password', 'mypas') BugzillaService.validate_config(self.service_config, 'mybz') die.assert_not_called() @mock.patch('bugwarrior.services.bz.die') def test_validate_config_api_key(self, die): self.config.set('mybz', 'bugzilla.base_uri', 'http://one.com/') self.config.set('mybz', 'bugzilla.username', 'me') self.config.set('mybz', 'bugzilla.api_key', '123') BugzillaService.validate_config(self.service_config, 'mybz') die.assert_not_called() @mock.patch('bugwarrior.services.bz.die') def test_validate_config_api_key_no_username(self, die): self.config.set('mybz', 'bugzilla.base_uri', 'http://one.com/') self.config.set('mybz', 'bugzilla.api_key', '123') BugzillaService.validate_config(self.service_config, 'mybz') die.assert_called_with("[mybz] has no 'bugzilla.username'") class TestBugzillaService(AbstractServiceTest, ServiceTest): SERVICE_CONFIG = { 'bugzilla.base_uri': 'http://one.com/', 'bugzilla.username': 'hello', 'bugzilla.password': 'there', } arbitrary_record = { 'product': 'Product', 'component': 'Something', 'priority': 'urgent', 'status': 'NEW', 'summary': 'This is the issue summary', 'id': 1234567, 'flags': [], } def setUp(self): super(TestBugzillaService, self).setUp() with mock.patch('bugzilla.Bugzilla'): self.service = self.get_mock_service(BugzillaService) def get_mock_service(self, *args, **kwargs): service = super(TestBugzillaService, self).get_mock_service( *args, **kwargs) service.bz = FakeBugzillaLib(self.arbitrary_record) return service def test_api_key_supplied(self): with mock.patch('bugzilla.Bugzilla'): self.service = self.get_mock_service( BugzillaService, config_overrides={ 'bugzilla.base_uri': 'http://one.com/', 'bugzilla.username': 'me', 'bugzilla.api_key': '123', }) def test_to_taskwarrior(self): arbitrary_extra = { 'url': 'http://path/to/issue/', 'annotations': [ 'Two', ], } issue = self.service.get_issue_for_record( self.arbitrary_record, arbitrary_extra, ) expected_output = { 'project': self.arbitrary_record['component'], 'priority': issue.PRIORITY_MAP[self.arbitrary_record['priority']], 'annotations': arbitrary_extra['annotations'], issue.STATUS: self.arbitrary_record['status'], issue.URL: arbitrary_extra['url'], issue.SUMMARY: self.arbitrary_record['summary'], issue.BUG_ID: self.arbitrary_record['id'], issue.PRODUCT: self.arbitrary_record['product'], issue.COMPONENT: self.arbitrary_record['component'], } actual_output = issue.to_taskwarrior() self.assertEqual(actual_output, expected_output) def test_issues(self): issue = next(self.service.issues()) expected = { 'annotations': [], 'bugzillabugid': 1234567, 'bugzillastatus': 'NEW', 'bugzillasummary': 'This is the issue summary', 'bugzillaurl': u'https://http://one.com//show_bug.cgi?id=1234567', 'bugzillaproduct': 'Product', 'bugzillacomponent': 'Something', 'description': u'(bw)Is#1234567 - This is the issue summary .. https://http://one.com//show_bug.cgi?id=1234567', 'priority': 'H', 'project': 'Something', 'tags': []} self.assertEqual(issue.get_taskwarrior_record(), expected) bugwarrior-1.5.1/setup.py0000644000175000017500000000636413111575223017340 0ustar threebeanthreebean00000000000000from setuptools import setup, find_packages version = '1.5.1' f = open('bugwarrior/README.rst') long_description = f.read().strip() long_description = long_description.split('split here', 1)[1] f.close() setup(name='bugwarrior', version=version, description="Sync github, bitbucket, and trac issues with taskwarrior", long_description=long_description, classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License (GPL)", "Programming Language :: Python :: 2", "Programming Language :: Python :: 3", "Topic :: Software Development :: Bug Tracking", "Topic :: Utilities", ], keywords='task taskwarrior todo github ', author='Ralph Bean', author_email='ralph.bean@gmail.com', url='http://github.com/ralphbean/bugwarrior', license='GPLv3+', packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), include_package_data=True, zip_safe=False, install_requires=[ "requests", "taskw >= 0.8", "python-dateutil", "pytz", "six>=1.9.0", "jinja2>=2.7.2", "dogpile.cache>=0.5.3", "lockfile>=0.9.1", "click", "future!=0.16.0", ], extras_require=dict( keyring=["keyring", "dbus-python"], jira=["jira>=0.22"], megaplan=["megaplan>=1.4"], activecollab=["pypandoc", "pyac>=0.1.5"], bts=["PySimpleSOAP","python-debianbts>=2.6.1"], trac=["offtrac"], bugzilla=["python-bugzilla"], ), tests_require=[ "Mock", "nose", "responses", "bugwarrior[jira]", "bugwarrior[megaplan]", "bugwarrior[activecollab]", "bugwarrior[bts]", "bugwarrior[trac]", "bugwarrior[bugzilla]", ], test_suite='nose.collector', entry_points=""" [console_scripts] bugwarrior-pull = bugwarrior:pull bugwarrior-vault = bugwarrior:vault bugwarrior-uda = bugwarrior:uda [bugwarrior.service] github=bugwarrior.services.github:GithubService gitlab=bugwarrior.services.gitlab:GitlabService bitbucket=bugwarrior.services.bitbucket:BitbucketService trac=bugwarrior.services.trac:TracService bts=bugwarrior.services.bts:BTSService bugzilla=bugwarrior.services.bz:BugzillaService teamlab=bugwarrior.services.teamlab:TeamLabService redmine=bugwarrior.services.redmine:RedMineService activecollab2=bugwarrior.services.activecollab2:ActiveCollab2Service activecollab=bugwarrior.services.activecollab:ActiveCollabService jira=bugwarrior.services.jira:JiraService megaplan=bugwarrior.services.megaplan:MegaplanService phabricator=bugwarrior.services.phab:PhabricatorService versionone=bugwarrior.services.versionone:VersionOneService pagure=bugwarrior.services.pagure:PagureService taiga=bugwarrior.services.taiga:TaigaService gerrit=bugwarrior.services.gerrit:GerritService trello=bugwarrior.services.trello:TrelloService youtrack=bugwarrior.services.youtrack:YoutrackService """, )