new application boobtracker (refs #684)
This commit is contained in:
parent
bc1d4921d0
commit
c911955a92
3 changed files with 396 additions and 0 deletions
25
scripts/boobtracker
Executable file
25
scripts/boobtracker
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright(C) 2011 Romain Bignon
|
||||
#
|
||||
# This file is part of weboob.
|
||||
#
|
||||
# weboob is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# weboob 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 Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from weboob.applications.boobtracker import BoobTracker
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
BoobTracker.run()
|
||||
23
weboob/applications/boobtracker/__init__.py
Normal file
23
weboob/applications/boobtracker/__init__.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright(C) 2011 Romain Bignon
|
||||
#
|
||||
# This file is part of weboob.
|
||||
#
|
||||
# weboob is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# weboob 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 Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from .boobtracker import BoobTracker
|
||||
|
||||
__all__ = ['BoobTracker']
|
||||
348
weboob/applications/boobtracker/boobtracker.py
Normal file
348
weboob/applications/boobtracker/boobtracker.py
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright(C) 2011 Romain Bignon
|
||||
#
|
||||
# This file is part of weboob.
|
||||
#
|
||||
# weboob is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# weboob 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 Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with weboob. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import sys
|
||||
|
||||
from weboob.capabilities.bugtracker import ICapBugTracker, Query, Update
|
||||
from weboob.tools.application.repl import ReplApplication
|
||||
from weboob.tools.application.formatters.iformatter import IFormatter
|
||||
from weboob.tools.misc import html2text
|
||||
|
||||
|
||||
__all__ = ['BoobTracker']
|
||||
|
||||
|
||||
class IssueFormatter(IFormatter):
|
||||
MANDATORY_FIELDS = ('id', 'project', 'title', 'body', 'author')
|
||||
|
||||
def flush(self):
|
||||
pass
|
||||
|
||||
def format_dict(self, item):
|
||||
result = u'%s%s - #%s - %s%s\n' % (self.BOLD, item['project'].name, item['id'], item['title'], self.NC)
|
||||
result += '\n%s\n\n' % item['body']
|
||||
result += 'Author: %s (%s)\n' % (item['author'].name, item['creation'])
|
||||
if item['status']:
|
||||
result += 'Status: %s\n' % item['status'].name
|
||||
if item['version']:
|
||||
result += 'Version: %s\n' % item['version'].name
|
||||
if item['category']:
|
||||
result += 'Category: %s\n' % item['category']
|
||||
if item['assignee']:
|
||||
result += 'Assignee: %s\n' % (item['assignee'].name)
|
||||
if item['attachments']:
|
||||
result += '\nAttachments:\n'
|
||||
for a in item['attachments']:
|
||||
result += '* %s%s%s <%s>\n' % (self.BOLD, a.filename, self.NC, a.url)
|
||||
if item['history']:
|
||||
result += '\nHistory:\n'
|
||||
for u in item['history']:
|
||||
result += '* %s%s - %s%s\n' % (self.BOLD, u.date, u.author, self.NC)
|
||||
if u.message:
|
||||
result += html2text(u.message)
|
||||
return result
|
||||
|
||||
class IssuesListFormatter(IFormatter):
|
||||
MANDATORY_FIELDS = ('id', 'project', 'status', 'title', 'category')
|
||||
|
||||
count = 0
|
||||
|
||||
def flush(self):
|
||||
self.count = 0
|
||||
pass
|
||||
|
||||
def format_dict(self, item):
|
||||
self.count += 1
|
||||
result = u'%s* (%s) %s - [%s] %s%s\n' % (self.BOLD, item['id'], item['project'].name, item['status'].name, item['title'], self.NC)
|
||||
result += ' %s' % (item['category'])
|
||||
return result
|
||||
|
||||
class BoobTracker(ReplApplication):
|
||||
APPNAME = 'boobtracker'
|
||||
VERSION = '0.9'
|
||||
COPYRIGHT = 'Copyright(C) 2011 Romain Bignon'
|
||||
DESCRIPTION = "Console application allowing to send messages on various websites and " \
|
||||
"to display message threads and contents."
|
||||
CAPS = ICapBugTracker
|
||||
EXTRA_FORMATTERS = {'issue_info': IssueFormatter,
|
||||
'issues_list': IssuesListFormatter,
|
||||
}
|
||||
COMMANDS_FORMATTERS = {'get': 'issue_info',
|
||||
'post': 'issue_info',
|
||||
'edit': 'issue_info',
|
||||
'search': 'issues_list',
|
||||
'ls': 'issues_list',
|
||||
}
|
||||
|
||||
def add_application_options(self, group):
|
||||
group.add_option('--author')
|
||||
group.add_option('--title')
|
||||
group.add_option('--assignee')
|
||||
group.add_option('--target-version', dest='version')
|
||||
group.add_option('--category')
|
||||
group.add_option('--status')
|
||||
|
||||
def do_search(self, line):
|
||||
"""
|
||||
search PROJECT
|
||||
|
||||
List issues for a project.
|
||||
|
||||
You can use these filters from command line:
|
||||
--author AUTHOR
|
||||
--title TITLE_PATTERN
|
||||
--assignee ASSIGNEE
|
||||
--target-version VERSION
|
||||
--category CATEGORY
|
||||
--status STATUS
|
||||
"""
|
||||
query = Query()
|
||||
|
||||
path = self.working_path.get()
|
||||
backends = []
|
||||
if line.strip():
|
||||
query.project, backends = self.parse_id(line, unique_backend=True)
|
||||
elif len(path) > 0:
|
||||
query.project = path[0]
|
||||
else:
|
||||
print >>sys.stderr, 'Please enter a project name'
|
||||
return 1
|
||||
|
||||
query.author = self.options.author
|
||||
query.title = self.options.title
|
||||
query.assignee = self.options.assignee
|
||||
query.version = self.options.version
|
||||
query.category = self.options.category
|
||||
query.status = self.options.status
|
||||
|
||||
self.change_path('/%s/search' % query.project)
|
||||
for backend, issue in self.do('iter_issues', query, backends=backends):
|
||||
self.add_object(issue)
|
||||
self.format(issue)
|
||||
self.flush()
|
||||
|
||||
def complete_get(self, text, line, *ignored):
|
||||
args = line.split(' ')
|
||||
if len(args) == 2:
|
||||
return self._complete_object()
|
||||
|
||||
def do_get(self, line):
|
||||
"""
|
||||
get ISSUE
|
||||
|
||||
Get an issue and display it.
|
||||
"""
|
||||
if not line:
|
||||
print >>sys.stderr, 'This command takes an argument: %s' % self.get_command_help('get', short=True)
|
||||
return 2
|
||||
|
||||
issue = self.get_object(line, 'get_issue')
|
||||
if not issue:
|
||||
print >>sys.stderr, 'Issue not found: %s' % line
|
||||
return 3
|
||||
self.format(issue)
|
||||
self.flush()
|
||||
|
||||
def complete_comment(self, text, line, *ignored):
|
||||
args = line.split(' ')
|
||||
if len(args) == 2:
|
||||
return self._complete_object()
|
||||
|
||||
def do_comment(self, line):
|
||||
"""
|
||||
comment ISSUE [TEXT]
|
||||
|
||||
Comment an issue. If no text is given, enter it in standard input.
|
||||
"""
|
||||
id, text = self.parse_command_args(line, 2, 1)
|
||||
if text is None:
|
||||
text = self.acquire_input()
|
||||
|
||||
id, backend_name = self.parse_id(id, unique_backend=True)
|
||||
update = Update(0)
|
||||
update.message = text
|
||||
|
||||
self.do('update_issue', id, update, backends=backend_name).wait()
|
||||
|
||||
def complete_remove(self, text, line, *ignored):
|
||||
args = line.split(' ')
|
||||
if len(args) == 2:
|
||||
return self._complete_object()
|
||||
|
||||
def do_remove(self, line):
|
||||
"""
|
||||
remove ISSUE
|
||||
|
||||
Remove an issue.
|
||||
"""
|
||||
id, backend_name = self.parse_id(line, unique_backend=True)
|
||||
self.do('remove_issue', id, backends=backend_name).wait()
|
||||
|
||||
ISSUE_FIELDS = (('title', (None, False)),
|
||||
('assignee', ('members', True)),
|
||||
('version', ('versions', True)),
|
||||
('category', ('categories', False)),
|
||||
('status', ('statuses', True)),
|
||||
)
|
||||
|
||||
def get_list_item(self, objects_list, name):
|
||||
if name is None:
|
||||
return None
|
||||
|
||||
for obj in objects_list:
|
||||
if obj.name.lower() == name.lower():
|
||||
return obj
|
||||
print 'Error: "%s" is not found' % name
|
||||
return None
|
||||
|
||||
def prompt_issue(self, issue, requested_key=None, requested_value=None):
|
||||
for key, (list_name, is_list_object) in self.ISSUE_FIELDS:
|
||||
if requested_key and requested_key != key:
|
||||
continue
|
||||
|
||||
if requested_value:
|
||||
value = requested_value
|
||||
elif not self.interactive:
|
||||
value = getattr(self.options, key)
|
||||
else:
|
||||
value = None
|
||||
|
||||
if sys.stdin.isatty():
|
||||
default = getattr(issue, key)
|
||||
if not default:
|
||||
default = None
|
||||
elif 'name' in dir(default):
|
||||
default = default.name
|
||||
if list_name is None:
|
||||
if value is not None:
|
||||
setattr(issue, key, value)
|
||||
print '%s: %s' % (key.capitalize(), value)
|
||||
continue
|
||||
setattr(issue, key, self.ask(key.capitalize(), default=default))
|
||||
else:
|
||||
objects_list = getattr(issue.project, list_name)
|
||||
if len(objects_list) == 0:
|
||||
continue
|
||||
|
||||
print '----------'
|
||||
if value is not None:
|
||||
if is_list_object:
|
||||
value = self.get_list_item(objects_list, value)
|
||||
if value is not None:
|
||||
setattr(issue, key, value)
|
||||
print '%s: %s' % (key.capitalize(), value.name)
|
||||
continue
|
||||
|
||||
while value is None:
|
||||
print 'Availables:', ', '.join([(o if isinstance(o, basestring) else o.name) for o in objects_list])
|
||||
if is_list_object and getattr(issue, key):
|
||||
default = getattr(issue, key).name
|
||||
else:
|
||||
default = getattr(issue, key) or ''
|
||||
text = self.ask(key.capitalize(), default=default)
|
||||
if not text:
|
||||
break
|
||||
if is_list_object:
|
||||
value = self.get_list_item(objects_list, text)
|
||||
else:
|
||||
value = text
|
||||
|
||||
if value is not None:
|
||||
setattr(issue, key, value)
|
||||
|
||||
def do_post(self, line):
|
||||
"""
|
||||
post PROJECT
|
||||
|
||||
Post a new issue.
|
||||
|
||||
If you are not in interactive mode, you can use these parameters:
|
||||
--title TITLE
|
||||
--assignee ASSIGNEE
|
||||
--target-version VERSION
|
||||
--category CATEGORY
|
||||
--status STATUS
|
||||
"""
|
||||
if not line.strip():
|
||||
print 'Please give the project name'
|
||||
return 1
|
||||
|
||||
project, backend_name = self.parse_id(line, unique_backend=True)
|
||||
|
||||
backend = self.weboob.get_backend(backend_name)
|
||||
issue = backend.create_issue(project)
|
||||
|
||||
self.prompt_issue(issue)
|
||||
if sys.stdin.isatty():
|
||||
print '----------'
|
||||
print 'Please enter the content of this new issue.'
|
||||
issue.body = self.acquire_input()
|
||||
|
||||
for backend, issue in self.weboob.do('post_issue', issue, backends=backend):
|
||||
if issue:
|
||||
print 'Issue %s%s@%s%s created' % (self.BOLD, issue.id, issue.backend, self.NC)
|
||||
|
||||
def complete_remove(self, text, line, *ignored):
|
||||
args = line.split(' ')
|
||||
if len(args) == 2:
|
||||
return self._complete_object()
|
||||
if len(args) == 3:
|
||||
return dict(self.ISSUE_FIELDS).keys()
|
||||
|
||||
def do_edit(self, line):
|
||||
"""
|
||||
edit ISSUE [KEY [VALUE]]
|
||||
|
||||
Edit an issue.
|
||||
If you are not in interactive mode, you can use these parameters:
|
||||
--title TITLE
|
||||
--assignee ASSIGNEE
|
||||
--target-version VERSION
|
||||
--category CATEGORY
|
||||
--status STATUS
|
||||
"""
|
||||
_id, key, value = self.parse_command_args(line, 3, 1)
|
||||
issue = self.get_object(_id, 'get_issue')
|
||||
if not issue:
|
||||
print >>sys.stderr, 'Issue not found: %s' % _id
|
||||
return 3
|
||||
|
||||
self.prompt_issue(issue, key, value)
|
||||
|
||||
for backend, i in self.weboob.do('post_issue', issue, backends=issue.backend):
|
||||
if i:
|
||||
print 'Issue %s%s@%s%s updated' % (self.BOLD, issue.id, issue.backend, self.NC)
|
||||
self.format(i)
|
||||
self.flush()
|
||||
|
||||
def complete_attach(self, text, line, *ignored):
|
||||
args = line.split(' ')
|
||||
if len(args) == 2:
|
||||
return self._complete_object()
|
||||
elif len(args) >= 3:
|
||||
return self.path_completer(args[2])
|
||||
|
||||
def do_attach(self, line):
|
||||
"""
|
||||
attach ISSUE FILENAME
|
||||
|
||||
Attach a file to an issue (Not implemented yet).
|
||||
"""
|
||||
print >>sys.stderr, 'Not implemented yet.'
|
||||
Loading…
Add table
Add a link
Reference in a new issue