def __init__(self, main_config, main_section, target): self.config = ServiceConfig(self.CONFIG_PREFIX, main_config, target) self.main_section = main_section self.main_config = main_config self.target = target self.desc_len = self._get_config_or_default('description_length', 35, asint) self.anno_len = self._get_config_or_default('annotation_length', 45, asint) self.inline_links = self._get_config_or_default( 'inline_links', True, asbool) self.annotation_links = self._get_config_or_default( 'annotation_links', not self.inline_links, asbool) self.annotation_comments = self._get_config_or_default( 'annotation_comments', True, asbool) self.shorten = self._get_config_or_default('shorten', False, asbool) self.default_priority = self._get_config_or_default( 'default_priority', 'M') self.add_tags = [] for raw_option in aslist(self.config.get('add_tags', '')): option = raw_option.strip(' +;') if option: self.add_tags.append(option) log.info("Working on [%s]", self.target)
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 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])
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')
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 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 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)
def __init__(self, main_config, main_section, target): self.config = ServiceConfig(self.CONFIG_PREFIX, main_config, target) self.main_section = main_section self.main_config = main_config self.target = target self.desc_len = self._get_config_or_default('description_length', 35, asint); self.anno_len = self._get_config_or_default('annotation_length', 45, asint); self.inline_links = self._get_config_or_default('inline_links', True, asbool); self.annotation_links = self._get_config_or_default('annotation_links', not self.inline_links, asbool) self.annotation_comments = self._get_config_or_default('annotation_comments', True, asbool) self.shorten = self._get_config_or_default('shorten', False, asbool) self.default_priority = self._get_config_or_default('default_priority','M') self.add_tags = [] for raw_option in aslist(self.config.get('add_tags', '')): option = raw_option.strip(' +;') if option: self.add_tags.append(option) log.info("Working on [%s]", self.target)
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.main_config = main_config self.target = target self.desc_len = self._get_config_or_default('description_length', 35, asint) self.anno_len = self._get_config_or_default('annotation_length', 45, asint) self.inline_links = self._get_config_or_default( 'inline_links', True, asbool) self.annotation_links = self._get_config_or_default( 'annotation_links', not self.inline_links, asbool) self.annotation_comments = self._get_config_or_default( 'annotation_comments', True, asbool) self.shorten = self._get_config_or_default('shorten', False, asbool) self.default_priority = self._get_config_or_default( 'default_priority', 'M') self.add_tags = [] for raw_option in aslist(self.config.get('add_tags', '')): option = raw_option.strip(' +;') if option: self.add_tags.append(option) log.info("Working on [%s]", self.target) def _get_config_or_default(self, key, default, as_type=lambda x: x): """Return a main config value, or default if it does not exist.""" if self.main_config.has_option(self.main_section, key): return as_type(self.main_config.get(self.main_section, key)) return default 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='******'): 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)) if service_config.has_option(target, 'default_priority'): die("[%s] has a 'default_priority' option. Should be " "'%s.default_priority'." % (target, cls.CONFIG_PREFIX)) if service_config.has_option(target, 'add_tags'): die("[%s] has an 'add_tags' option. Should be " "'%s.add_tags'." % (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
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.main_config = main_config self.target = target self.desc_len = self._get_config_or_default('description_length', 35, asint); self.anno_len = self._get_config_or_default('annotation_length', 45, asint); self.inline_links = self._get_config_or_default('inline_links', True, asbool); self.annotation_links = self._get_config_or_default('annotation_links', not self.inline_links, asbool) self.annotation_comments = self._get_config_or_default('annotation_comments', True, asbool) self.shorten = self._get_config_or_default('shorten', False, asbool) self.default_priority = self._get_config_or_default('default_priority','M') self.add_tags = [] for raw_option in aslist(self.config.get('add_tags', '')): option = raw_option.strip(' +;') if option: self.add_tags.append(option) log.info("Working on [%s]", self.target) def _get_config_or_default(self, key, default, as_type=lambda x: x): """Return a main config value, or default if it does not exist.""" if self.main_config.has_option(self.main_section, key): return as_type(self.main_config.get(self.main_section, key)) return default 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='******'): 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)) if service_config.has_option(target, 'default_priority'): die("[%s] has a 'default_priority' option. Should be " "'%s.default_priority'." % (target, cls.CONFIG_PREFIX)) if service_config.has_option(target, 'add_tags'): die("[%s] has an 'add_tags' option. Should be " "'%s.add_tags'." % (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