~ossug-hychen/lp-cli/trunk

« back to all changes in this revision

Viewing changes to mazo/extensions/lp_cli/__init__.py

  • Committer: Hsin-Yi Chen (hychen)
  • Date: 2012-07-18 08:28:51 UTC
  • mfrom: (76.1.21 develop)
  • Revision ID: hychen@canonical.com-20120718082851-k49sn8sak32yx2kk
Merge develop branch

[Feature]

* lp-editbugtasks - support to change milestone and add a bug comemnt

[Other]

* add comments of each classes in lp-cli extension
* refactory lp_cli.ExtensionDupbugtasks and lp_cli.ExtensionEditbugtasks

Show diffs side-by-side

added added

removed removed

Lines of Context:
22
22
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23
23
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24
24
# DEALINGS IN THE SOFTWARE.
 
25
from mazo.extensionlib import ExtensionType
 
26
from mazo.missionlib import CreateMissionJob, DoMissionJob, FormatDataJob, ModifyMissionJob
 
27
from mazo.cmdlib import Command
 
28
from mazo import util
 
29
 
25
30
import re
26
 
import sys
27
31
import logging
28
32
import subprocess
29
33
import tempfile
30
 
 
31
 
from mazo.extensionlib import ExtensionType
32
 
from mazo.missionlib import CreateMissionJob, DoMissionJob, FormatDataJob, ModifyMissionJob
33
 
from mazo.cmdlib import Command
34
 
from mazo import util
35
 
 
36
34
import dialog
37
35
import yaml
38
36
import os
39
37
import datetime
40
38
from launchpadlib.launchpad import Launchpad
41
39
 
42
 
class EntryNotFound(Exception): pass
43
 
 
 
40
# -----------------------------------------------------------------------
 
41
# Global Variables
 
42
# -----------------------------------------------------------------------
44
43
LP_VALIDATE_BUGTASK_STATUS=("New",
45
44
                    "Incomplete",
46
45
                    "Invalid",
55
54
                    'Critical',
56
55
                    'High',
57
56
                    'Medium',
58
 
 
59
57
                    'Low')
 
58
 
 
59
# -----------------------------------------------------------------------
 
60
# Utilitize Functions
 
61
# -----------------------------------------------------------------------
60
62
def get_bugtask(project, bug):
61
63
    "Get the bug's bug-task for this project"
62
64
    bugtasks = filter(lambda bt: bt.target == project, bug.bug_tasks)
65
67
    except IndexError:
66
68
        return None
67
69
 
68
 
class DoLPQueryJob(DoMissionJob):
69
 
 
70
 
    lp = None
71
 
 
72
 
    def do_prejob(self):
73
 
        self.connect_lp()
74
 
        super(DoLPQueryJob, self).do_prejob()
75
 
 
76
 
    def connect_lp(self):
77
 
        if not self.lp:
78
 
            system = os.getenv('LPSYSTEM') or 'production'
79
 
            self.logger.info("Connecting Launchpad {} server".format(system))
80
 
            cachedir = os.path.expanduser("~/.launchpadlib/cache")
81
 
            self.lp = Launchpad.login_with('lp-cli', system, cachedir)
82
 
        return self.lp
83
 
 
84
 
    def get_lp_entry(self, entry_type, entry_id, strict=True):
85
 
        if entry_type != 'people':
86
 
            entry_type = entry_type+'s'
 
70
def download_bug_attachments(bug, output_dir):
 
71
    # write title and desc
 
72
    with open(os.path.join(output_dir, bug.title.encode('utf-8')), 'w') as descfd:
 
73
            descfd.write(bug.description)
 
74
    for attachment in bug.attachments:
 
75
        filename = os.path.join(output_dir, attachment.title)
87
76
        try:
88
 
            return getattr(self.lp, entry_type)[entry_id]
89
 
        except KeyError:
90
 
            if strict:
91
 
                raise EntryNotFound('Can not find {0} in Launchpad {1}'.format(entry_id, entry_type))
92
 
            else:
93
 
                return None
94
 
 
95
 
class ExtensionGet(object):
96
 
    __metaclass__ = ExtensionType
97
 
    responsible_for = CreateMissionJob
98
 
    interface = Command
99
 
 
100
 
    def custom_arguments(self):
101
 
        self.add_argument('entry_type',
102
 
                                choices=('bug', ),
103
 
                                help='sepecify entry type that search bugs in')
104
 
        self.add_argument('entry_id',
105
 
                                nargs='+',
106
 
                                help='sepcify entry id that search bugs in')
107
 
 
108
 
    def maintask(self, args):
109
 
        for entry_id in args.entry_id:
110
 
            ret = self.get_lp_entry(args.entry_type, entry_id, False)
111
 
            if ret:
112
 
                yield ret
113
 
 
114
 
class ExtensionSearchbugtasks(object):
115
 
    __metaclass__ = ExtensionType
116
 
    responsible_for = CreateMissionJob
117
 
    interface = Command
118
 
 
119
 
    def custom_arguments(self):
120
 
        self.add_argument('entry_type',
121
 
                                choices=('people','project'),
122
 
                                help='sepecify entry type that search bugs in')
123
 
        self.add_argument('entry_id',
124
 
                                help='sepcify entry id that search bugs in')
125
 
        self.add_argument('--tag', dest='tags', action='append',
126
 
                                help='sepecify a bug tag')
127
 
        self.add_argument('--tags-combinator', dest='tags_combinator',
128
 
                                help='Search for any or all of the tags specified.',
129
 
                                choices=('Any', 'All'), default='Any')
130
 
        self.add_argument('--status', dest='status',
131
 
                                choices=LP_VALIDATE_BUGTASK_STATUS,
132
 
                                action='append',
133
 
                                help='sepecify bugtask status')
134
 
        self.add_argument('--importance',
135
 
                                choices=LP_VALIDATE_BUGTASK_IMPORTANCE,
136
 
                                action='append',
137
 
                                dest='importance',
138
 
                                help='sepecify bugtask importance')
139
 
        self.add_argument('--assignee', dest='assignee',
140
 
                                help='sepecify bug assignee')
141
 
        self.add_argument('--search-text', dest='search_text',
142
 
                                help='search text')
143
 
        self.add_argument('--milestone', dest='milestone', help='milestone name')
144
 
        self.add_argument('--package', dest='package', help='source package name (only for project)', action='append')
145
 
 
146
 
    def do_prejob(self):
147
 
        if self.cmd_args.package and self.cmd_args.entry_type != 'project':
148
 
            self.logger.error("package option is only for project entry")
149
 
 
150
 
    def maintask(self, args):
151
 
        entry = self.get_lp_entry(args.entry_type, args.entry_id)
152
 
        kwargs = self.transform_args(args, ('entry_id', 'entry_type', 'package'))
153
 
        if kwargs.get('assignee'):
154
 
            kwargs['assignee'] = self.lp.people[kwargs['assignee']]
155
 
        if kwargs.get('milestone'):
156
 
            kwargs['milestone'] = entry.getMilestone(name=kwargs['milestone'])
157
 
 
158
 
        if args.package and args.entry_type == 'project':
159
 
            ret = []
160
 
            for pkgname in args.package:
161
 
                srcpkg = entry.getSourcePackage(name=pkgname)
162
 
                ret.extend([e for e in srcpkg.searchTasks(**kwargs)])
163
 
            return ret
164
 
        else:
165
 
            return entry.searchTasks(**kwargs)
166
 
 
 
77
            data = attachment.data.open()
 
78
            with open(filename, 'w') as f:
 
79
                f.write(data.read())
 
80
            data.close()
 
81
        except Exception, e:
 
82
            logging.debug(e)
 
83
            logging.error(
 
84
                    "Unable to download file {0}".format(attachment.title))
 
85
 
 
86
def m_extract_bugs(result):
 
87
    #@FIXME: check entry type
 
88
    if type(result).__name__ != 'generator':
 
89
        return [bt.bug for bt in result]
 
90
    else:
 
91
        return result
167
92
 
168
93
# userEditSTring and EditableData codes is wrote by  James Ferguson <james.ferguson@canonical.com>, modified by hychen
169
94
def userEditString(suffix = '',
190
115
 
191
116
    return result.lstrip()
192
117
 
 
118
# -----------------------------------------------------------------------
 
119
# GUI Helper Functions
 
120
# -----------------------------------------------------------------------
 
121
__DLG = dialog.Dialog()
 
122
 
 
123
def ask_which_bugtasks(sel_msg, confirm_msg, origbugtasks):
 
124
    return ask_which_entries(sel_msg, confirm_msg, origbugtasks,
 
125
                          tag=lambda bugtask: str(bugtask.bug.id),
 
126
                          transform_cb=lambda bugtask: (str(bugtask.bug.id), bugtask.bug.title, 1),
 
127
                          result_format_cb=lambda bugtask: u"{0}-{1}".format(bugtask.bug.id, bugtask.bug.title))
 
128
 
 
129
def ask_which_bugs(sel_msg, confirm_msg, origbugs):
 
130
    return ask_which_entries(sel_msg, confirm_msg, origbugs,
 
131
                          tag=lambda bug: str(bug.id),
 
132
                          transform_cb=lambda bug: (str(bug.id), bug.title, 1),
 
133
                          result_format_cb=lambda bug: u"{0}-{1}".format(bug.id, bug.title))
 
134
 
 
135
def exit_if_user_cancelled(msg, lines):
 
136
    if not ask_confirmation(msg, lines):
 
137
        exit()
 
138
 
 
139
def ask_which_entries(selection_msg, confirmation_msg, pooldata,
 
140
                      tag=None, transform_cb=None, result_format_cb=None):
 
141
    selected_tags = ask_selection(selection_msg, pooldata, transform_cb)
 
142
    if not selected_tags:
 
143
        print u"Canceld because no selected bug"
 
144
        exit()
 
145
 
 
146
    if tag:
 
147
        selected_data = filter(lambda e: tag(e) in selected_tags, pooldata)
 
148
    else:
 
149
        selected_data = filter(lambda e: e in selected_tags, pooldata)
 
150
 
 
151
    # if selected data is empty means logic error
 
152
    assert selected_data
 
153
 
 
154
    # prepare confirmation message
 
155
    confirmation_msg += '\n'
 
156
    if result_format_cb:
 
157
        confirmation_msg += '\n'.join(map(result_format_cb, selected_data))
 
158
    else:
 
159
        confirmation_msg += '\n'.join(selected_data)
 
160
    confirmation_msg += '\n'
 
161
    exit_if_user_cancelled(confirmation_msg, len(selected_data))
 
162
    return selected_data
 
163
 
 
164
def ask_confirmation(msg, lines=40):
 
165
    return __DLG.yesno(msg, 40+lines*2, 74, defaultno=True) == 0
 
166
 
 
167
def ask_selection(msg, choices, transform_cb=None):
 
168
    if transform_cb:
 
169
        choices = map(transform_cb, choices)
 
170
    count_choices = len(choices)
 
171
    (code, tags) = __DLG.checklist(msg, 20+count_choices*2, 74,
 
172
                                    10+count_choices*2, choices)
 
173
    # exit if cancel
 
174
    if code != 0:
 
175
        exit()
 
176
    return tags
 
177
 
 
178
# -----------------------------------------------------------------------
 
179
# Exception Classes
 
180
# -----------------------------------------------------------------------
 
181
class EntryNotFound(Exception):
 
182
    pass
 
183
 
 
184
# -----------------------------------------------------------------------
 
185
# Utilitize Classes
 
186
# -----------------------------------------------------------------------
193
187
class EditableData():
194
188
    """Class to manage editable data of some text and some NVPs.  Set
195
189
    baseText and namedValues, call userEdit, then examine them again."""
250
244
                    cond[k] = v
251
245
            return cond
252
246
 
 
247
# -----------------------------------------------------------------------
 
248
# Job Classes
 
249
# -----------------------------------------------------------------------
 
250
 
 
251
## \breif A DoMissionJob to connect Launchpad before fetching data
 
252
#
 
253
class DoLPQueryJob(DoMissionJob):
 
254
 
 
255
    lp = None
 
256
 
 
257
    def do_prejob(self):
 
258
        self.connect_lp()
 
259
        super(DoLPQueryJob, self).do_prejob()
 
260
 
 
261
    def connect_lp(self):
 
262
        if not self.lp:
 
263
            system = os.getenv('LPSYSTEM') or 'production'
 
264
            self.logger.info("Connecting Launchpad {} server".format(system))
 
265
            cachedir = os.path.expanduser("~/.launchpadlib/cache")
 
266
            self.lp = Launchpad.login_with('lp-cli', system, cachedir)
 
267
        return self.lp
 
268
 
 
269
    def get_lp_entry(self, entry_type, entry_id, strict=True):
 
270
        if entry_type != 'people':
 
271
            entry_type = entry_type+'s'
 
272
        try:
 
273
            return getattr(self.lp, entry_type)[entry_id]
 
274
        except KeyError:
 
275
            if strict:
 
276
                raise EntryNotFound('Can not find {0} in Launchpad {1}'.format(entry_id, entry_type))
 
277
            else:
 
278
                return None
 
279
 
 
280
# -----------------------------------------------------------------------
 
281
# Extension Classes
 
282
# -----------------------------------------------------------------------
 
283
 
 
284
## \breif A Extension to create a mission message that maintask is to get
 
285
#         a entry from Launchpad.
 
286
#
 
287
# STDIN : None
 
288
# STDOUT: Encoded MessionMessage String
 
289
class ExtensionGet(object):
 
290
    __metaclass__ = ExtensionType
 
291
    responsible_for = CreateMissionJob
 
292
    interface = Command
 
293
 
 
294
    def custom_arguments(self):
 
295
        self.add_argument('entry_type',
 
296
                                choices=('bug', ),
 
297
                                help='sepecify entry type that search bugs in')
 
298
        self.add_argument('entry_id',
 
299
                                nargs='+',
 
300
                                help='sepcify entry id that search bugs in')
 
301
 
 
302
    def maintask(self, args):
 
303
        for entry_id in args.entry_id:
 
304
            ret = self.get_lp_entry(args.entry_type, entry_id, False)
 
305
            if ret:
 
306
                yield ret
 
307
 
 
308
## \breif A Extension to create a mission message that maintask is to
 
309
#         search bugtasks in a entry from Launchpad
 
310
#
 
311
# STDIN : None
 
312
# STDOUT: Encoded MessionMessage String
 
313
class ExtensionSearchbugtasks(object):
 
314
    __metaclass__ = ExtensionType
 
315
    responsible_for = CreateMissionJob
 
316
    interface = Command
 
317
 
 
318
    def custom_arguments(self):
 
319
        self.add_argument('entry_type',
 
320
                                choices=('people','project'),
 
321
                                help='sepecify entry type that search bugs in')
 
322
        self.add_argument('entry_id',
 
323
                                help='sepcify entry id that search bugs in')
 
324
        self.add_argument('--tag', dest='tags', action='append',
 
325
                                help='sepecify a bug tag')
 
326
        self.add_argument('--tags-combinator', dest='tags_combinator',
 
327
                                help='Search for any or all of the tags specified.',
 
328
                                choices=('Any', 'All'), default='Any')
 
329
        self.add_argument('--status', dest='status',
 
330
                                choices=LP_VALIDATE_BUGTASK_STATUS,
 
331
                                action='append',
 
332
                                help='sepecify bugtask status')
 
333
        self.add_argument('--importance',
 
334
                                choices=LP_VALIDATE_BUGTASK_IMPORTANCE,
 
335
                                action='append',
 
336
                                dest='importance',
 
337
                                help='sepecify bugtask importance')
 
338
        self.add_argument('--assignee', dest='assignee',
 
339
                                help='sepecify bug assignee')
 
340
        self.add_argument('--search-text', dest='search_text',
 
341
                                help='search text')
 
342
        self.add_argument('--milestone', dest='milestone', help='milestone name')
 
343
        self.add_argument('--package', dest='package', help='source package name (only for project)', action='append')
 
344
 
 
345
    def do_prejob(self):
 
346
        if self.cmd_args.package and self.cmd_args.entry_type != 'project':
 
347
            self.logger.error("package option is only for project entry")
 
348
 
 
349
    def maintask(self, args):
 
350
        entry = self.get_lp_entry(args.entry_type, args.entry_id)
 
351
        kwargs = self.transform_args(args, ('entry_id', 'entry_type', 'package'))
 
352
        if kwargs.get('assignee'):
 
353
            kwargs['assignee'] = self.get_lp_entry('people', [kwargs['assignee']])
 
354
        if kwargs.get('milestone'):
 
355
            kwargs['milestone'] = entry.getMilestone(name=kwargs['milestone'])
 
356
 
 
357
        if args.package and args.entry_type == 'project':
 
358
            ret = []
 
359
            for pkgname in args.package:
 
360
                srcpkg = entry.getSourcePackage(name=pkgname)
 
361
                ret.extend([e for e in srcpkg.searchTasks(**kwargs)])
 
362
            return ret
 
363
        else:
 
364
            return entry.searchTasks(**kwargs)
 
365
 
 
366
## \breif A Extension to create a mission message that modified mantask
 
367
#           arguments.
 
368
#
 
369
# STDIN : Encoded MissionMessage String
 
370
# STDOUT: Encoded MessionMessage String
253
371
class ExtensionApplyconds(object):
254
372
    __metaclass__ = ExtensionType
255
373
    responsible_for = ModifyMissionJob
272
390
        else:
273
391
            return cond.items()
274
392
 
 
393
## \breif A Extension to edit bugtasks collected from Launchpad
 
394
#
 
395
# STDIN : Encoded MissionMessage String
 
396
# STDOUT: None
275
397
class ExtensionEditbugtasks(object):
276
398
    __metaclass__ = ExtensionType
277
399
    responsible_for = DoLPQueryJob
278
400
    interface = Command
279
401
 
280
 
    def do_postjob(self):
281
 
        args = self.cmd_args
282
 
        dlg = dialog.Dialog()
283
 
 
284
 
        bugtasks = self.result
285
 
        kwargs = self.transform_args(args, ['yes'])
286
 
        for bugtask in bugtasks:
287
 
            for attr, attrval in kwargs.items():
288
 
                if not attrval:
289
 
                    continue
290
 
 
291
 
                if args.yes:
292
 
                    confirm = True
293
 
                else:
294
 
                    confirm_msg = "Change {} {} to {}?".format(bugtask.title, attr, attrval)
295
 
                    confirm = dlg.yesno(confirm_msg, 10, 74, defaultno=True) == 0
296
 
 
297
 
                if confirm:
298
 
                    # skip if new attribute value is empty
299
 
                    if attrval is None:
300
 
                        continue
301
 
                    # need special way to modify attribute
302
 
                    # or no need
303
 
                    if attr == 'assignee':
304
 
                        attrval = self.get_lp_entry('people', attrval)
305
 
                    setattr(bugtask, attr, attrval)
306
 
 
307
 
                    # inform user the change is applied
308
 
                    bugtask.lp_save()
309
 
                    if not args.yes:
310
 
                        dlg.msgbox('Done', 10, 74)
311
 
 
312
402
    def custom_arguments(self):
313
403
        self.add_argument('--status',
314
404
                      choices=LP_VALIDATE_BUGTASK_STATUS,
315
405
                      help='change status')
316
406
        self.add_argument('--assignee',
317
407
                      help='assigne to people')
 
408
        self.add_argument('--milestone',
 
409
                      help='milestone name')
318
410
        self.add_argument('-y', '--yes',
319
 
                         action='store_true',
320
 
                         help='auto anwser yes to question')
321
 
 
 
411
                      action='store_true',
 
412
                      help='auto anwser yes to question')
 
413
        self.add_argument('-m', '--message',
 
414
                      help='add a comment')
 
415
 
 
416
    def do_postjob(self):
 
417
        # process args
 
418
        kwargs = self.transform_args(self.cmd_args, ['yes', 'message'])
 
419
 
 
420
        # ask user to select bugtasks
 
421
        if self.cmd_args.yes:
 
422
            selected_bugtasks = self.result
 
423
        else:
 
424
            substr_update_attributes = []
 
425
            for k,v in kwargs.items():
 
426
                if not v:
 
427
                    continue
 
428
                substr_update_attributes.append(u"{0}:{1}".format(k,v))
 
429
 
 
430
            attr_msg = u"New attribute vlaues of each bugtasks:\n"
 
431
            attr_msg += u'\n'.join(substr_update_attributes)
 
432
 
 
433
            if self.cmd_args.message:
 
434
                attr_msg += "\nComment:{0}".format(self.cmd_args.message)
 
435
 
 
436
            sel_msg = u"Please select which bugtasks need to change attrbiutes\n\n"
 
437
            sel_msg += attr_msg
 
438
 
 
439
            confirmation_msg = u"Are you sure to modify these bugtasks?\n\n" + attr_msg
 
440
            selected_bugtasks = ask_which_bugtasks(sel_msg, confirmation_msg, self.result)
 
441
 
 
442
        for bugtask in selected_bugtasks:
 
443
            if self.cmd_args.message:
 
444
                print u"Add new comment"
 
445
                bugtask.bug.newMessage(content=self.cmd_args.message)
 
446
            if kwargs:
 
447
                self.editbugtaskattrs(bugtask, kwargs)
 
448
            bugtask.lp_save()
 
449
        print "Done!"
 
450
 
 
451
    def editbugtaskattrs(self, bugtask, kwargs):
 
452
        print u"Editing bugtask {0}".format(bugtask.title)
 
453
        for attr, attrval in kwargs.items():
 
454
            # skip if new attribute value is empty
 
455
            if attrval is None:
 
456
                continue
 
457
 
 
458
            # convert bug_target to a project
 
459
            target = self.lp.load(bugtask.target.self_link)
 
460
 
 
461
            # get old value
 
462
            orig_attr_val = getattr(bugtask, attr)
 
463
 
 
464
            # get new value
 
465
            if attr == 'assignee':
 
466
                attrval = self.get_lp_entry('people', attrval)
 
467
            if attr == 'milestone':
 
468
                milestone_name = attrval
 
469
                attrval = target.getMilestone(name=attrval)
 
470
                # Skip because milestone is not exists
 
471
                if not attrval:
 
472
                    print u"Skip to update {0}: Milestone {0} is not exists in {1}".format(milestone_name, target.name)
 
473
                    continue
 
474
 
 
475
            # skip if old and new attribute is same
 
476
            # otherwise update it
 
477
            if orig_attr_val == attrval:
 
478
                print u"Skip to update {0}: move {1} to {2}".format(attr, orig_attr_val, attrval)
 
479
            else:
 
480
                print u"Update {0}: move {1} to {2}".format(attr, orig_attr_val, attrval)
 
481
                setattr(bugtask, attr, attrval)
 
482
 
 
483
## \breif A Abstract Extension Class to format collected data
322
484
class ExtensionFormatdata(object):
323
485
    __metaclass__ = ExtensionType
324
486
    responsible_for = DoLPQueryJob, FormatDataJob
327
489
    def custom_arguments(self):
328
490
        self.add_argument('target')
329
491
 
 
492
## \breif A Extension to print bugs collected from Launchpad
 
493
#
 
494
# STDIN : Encoded MissionMessage String
 
495
# STDOUT: string
330
496
class ExtensionPrintbugs(ExtensionFormatdata):
331
497
 
332
498
    def target_list(self):
335
501
            for task in bug.bug_tasks:
336
502
                print u" {0}: {1}".format(task.bug_target_name, task.status)
337
503
 
 
504
## \breif A Extension to print bugtasks collected from Launchpad
 
505
#
 
506
# STDIN : Encoded MissionMessage String
 
507
# STDOUT: string
338
508
class ExtensionPrintbugtasks(ExtensionFormatdata):
339
509
 
340
510
    def custom_arguments(self):
367
537
    def print_bugtask(self, bugtask):
368
538
        print u"{} {} ({})".format(bugtask.bug.id, bugtask.bug.title, bugtask.web_link)
369
539
 
 
540
## \breif A Extension to duplicate bugtasks collected from Launchpad
 
541
#
 
542
# STDIN : Encoded MissionMessage String
 
543
# STDOUT: None
370
544
class ExtensionDupbugtasks(object):
371
545
    __metaclass__ = ExtensionType
372
546
    responsible_for = DoLPQueryJob
397
571
        self.add_argument('--remove-tag', dest='remove_tag', action='append')
398
572
 
399
573
    def do_postjob(self):
 
574
        # process args
400
575
        prj = [self.get_lp_entry('project', prj_name) \
401
576
                for prj_name in self.cmd_args.project]
402
577
        self.target_project = prj[0]
410
585
        else:
411
586
            self.target_assignee = None
412
587
 
413
 
        #@FIXME: extract dlg to super class
414
 
        self.dlg = dialog.Dialog()
415
 
        self.logger.info(u"Result type is Bugtasks, convert to Bugs")
416
 
        origbugs = [bugtask.bug for bugtask in self.result]
 
588
        origbugs = m_extract_bugs(self.result)
417
589
        # ask user to decide which bugs need to be duplicated
418
 
        selected_bugids = self._ask_selection(origbugs)
419
 
        selected_bugs = filter(lambda e: str(e.id) in selected_bugids, origbugs)
420
 
 
421
 
        # exit if not selected bugs
422
 
        if not selected_bugs:
423
 
            self.logger.info(u"Canceld because no selected bug")
424
 
            exit()
425
 
 
426
 
        # ask for confirmation
427
 
        msg = u"You will duplicate theses bugs to {0}\n".format(self.target_project.name)
428
 
        msg += '\n'.join([u"{0}-{1}".format(bug.id, bug.title) for bug in selected_bugs])
429
 
        msg += '\n'
430
 
        self._ask_confirmation(msg)
 
590
        sel_msg = u'Do you want to duplicate these bug to {0}'.format(self.target_project.name)
 
591
        confirm_msg = u"You will duplicate theses bugs to {0}\n".format(self.target_project.name)
 
592
        selected_bugs = ask_which_bugs(sel_msg, confirm_msg, origbugs)
431
593
 
432
594
        # create bugtasks
433
595
        created_bugtasks = []
467
629
                              origbug.description,
468
630
                              self.cmd_args.append_desc)
469
631
 
470
 
        #@TODO: Allow user to modify editable data
 
632
        # ask user to modify editable data
471
633
        editable_data = self._ask_predit("%s\n\nCopied from bug #%d" % (newdesc, origbug.id), newtags, newtitle)
472
634
 
473
635
        newbug = self.lp.bugs.createBug(
509
671
            msg = u"Unable to set a bugtask's importance and status"
510
672
            self.logger.warning(msg)
511
673
 
512
 
    def _ask_selection(self, bug):
513
 
        choices = map(lambda e: (str(e.id), e.title, 1) , bug)
514
 
        (code, tags) = self.dlg.checklist(u"Select bugs you want to duplicate",
515
 
                            len(bug)*2, 74, len(bug)*2, choices)
516
 
        # exit if cancel
517
 
        if code != 0:
518
 
            exit()
519
 
        return tags
520
 
 
521
674
    def _ask_predit(self, desc, tags, title):
522
675
        editableData = EditableData(desc,
523
676
            {"Projects" : " ".join(self.cmd_args.project),
526
679
        editableData.userEdit()
527
680
        return editableData
528
681
 
529
 
    def _ask_confirmation(self, msg):
530
 
        if self.dlg.yesno(msg, 10, 74, defaultno=True) != 0:
531
 
                        exit()
532
 
 
533
 
def download_bug_attachments(bug, output_dir):
534
 
    # write title and desc
535
 
    with open(os.path.join(output_dir, bug.title.encode('utf-8')), 'w') as descfd:
536
 
            descfd.write(bug.description)
537
 
    for attachment in bug.attachments:
538
 
        filename = os.path.join(output_dir, attachment.title)
539
 
        try:
540
 
            data = attachment.data.open()
541
 
            with open(filename, 'w') as f:
542
 
                f.write(data.read())
543
 
            data.close()
544
 
        except Exception, e:
545
 
            logging.debug(e)
546
 
            logging.error(
547
 
                    "Unable to download file {0}".format(attachment.title))
548
 
 
 
682
## \breif A Extension to download attachments of bugtasks collected from Launchpad
 
683
#
 
684
# STDIN : Encoded MissionMessage String
 
685
# STDOUT: None
549
686
class ExtensionGrabbugattachments(object):
550
687
    __metaclass__ = ExtensionType
551
688
    responsible_for = DoLPQueryJob
555
692
        self.add_argument('--output-dir', default=os.getcwd())
556
693
 
557
694
    def do_postjob(self):
 
695
        bugs = m_extract_bugs(self.result)
 
696
 
 
697
        # create sub directory
558
698
        self.output_dir = self.cmd_args.output_dir
559
 
        #@FIXME:
560
 
        if type(self.result).__name__ != 'generator':
561
 
            bugs = [bt.bug for bt in self.result]
562
 
        else:
563
 
            bugs = self.result
564
 
 
565
 
        # create sub directory
566
699
        for bug in bugs:
567
700
            subdir_name = os.path.join(self.output_dir, str(bug.id))
568
701
            if not os.path.isdir(subdir_name):