GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( 598b8e...6df1c2 )
by Andrew
01:03
created

JugglerTaskStart.get_value()   A

Complexity

Conditions 2

Size

Total Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
c 1
b 0
f 0
dl 0
loc 6
rs 9.4285
1
#! /usr/bin/python
2
3
"""
4
generic to task-juggler extraction script
5
6
This script queries generic, and generates a task-juggler input file in order to generate a gant-chart.
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (103/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
7
"""
8
9
import logging,tempfile,subprocess,datetime,icalendar,shutil,os
0 ignored issues
show
Coding Style introduced by
Exactly one space required after comma
Loading history...
introduced by
Multiple imports on one line (logging, tempfile, subprocess, datetime, icalendar, shutil, os)
Loading history...
introduced by
Unable to import 'icalendar'
Loading history...
Unused Code introduced by
The import shutil seems to be unused.
Loading history...
introduced by
Imports from package logging are not grouped
Loading history...
10
from collections import OrderedDict
11
12
DEFAULT_LOGLEVEL = 'warning'
13
DEFAULT_OUTPUT = 'export.tjp'
14
15
TAB = ' ' * 4
16
17
def is_number(s):
18
    try:
19
        float(s)
20
        return True
21
    except ValueError:
22
        return False
23
24
def set_logging_level(loglevel):
25
    '''
26
    Set the logging level
27
28
    Args:
29
        loglevel String representation of the loglevel
30
    '''
31
    numeric_level = getattr(logging, loglevel.upper(), None)
32
    if not isinstance(numeric_level, int):
33
        raise ValueError('Invalid log level: %s' % loglevel)
34
    logging.basicConfig(level=numeric_level)
35
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
36
def to_tj3time(dt):
37
    """
38
    Convert python date or datetime object to TJ3 format
39
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
40
    """
41
    return dt.isoformat().replace("T", "-").split(".")[0]
42
43
def to_tj3interval(start, end):
44
    return "%s - %s" % (to_tj3time(start), to_tj3time(end))
45
46
TJP_NUM_ID_PREFIX = "tjp_numid_"
47
TJP_DASH_PREFIX = "__DASH__"
48
TJP_SPACE_PREFIX = "__SPACE__"
49
50
def to_identifier(key):
51
    '''
52
    Convert given key to identifier, interpretable by TaskJuggler as a task-identifier
53
54
    Args:
55
        key (str): Key to be converted
56
57
    Returns:
58
        str: Valid task-identifier based on given key
59
    '''
60
    if is_number(key):
61
        key = TJP_NUM_ID_PREFIX+str(key)
62
    key = key.replace('-', TJP_DASH_PREFIX).replace(" ", TJP_SPACE_PREFIX)
63
    return key
64
65
def from_identifier(key):
66
    if TJP_NUM_ID_PREFIX in key:
67
        return int(key.replace(TJP_NUM_ID_PREFIX, ""))
68
    return key.replace(TJP_DASH_PREFIX, "-").replace(TJP_SPACE_PREFIX, " ")
69
70
class JugglerTaskProperty(object):
71
    '''Class for a property of a Task Juggler'''
72
73
    DEFAULT_NAME = 'property name'
74
    DEFAULT_VALUE = 'not initialized'
75
    PREFIX = ''
76
    SUFFIX = ''
77
    TEMPLATE = TAB + '{prop} {value}\n'
78
    VALUE_TEMPLATE = '{prefix}{value}{suffix}'
79
80
81
    def __init__(self, issue=None):
82
        '''
83
        Initialize task juggler property
84
85
        Args:
86
            issue (class): The generic issue to load from
87
            value (object): Value of the property
88
        '''
89
        self.name = self.DEFAULT_NAME
90
        self.set_value(self.DEFAULT_VALUE)
91
        self.empty = False
92
        self.parent = None
93
        self.load_default_properties(issue)
94
95
        if issue:
96
            if self.load_from_issue(issue) is False:
97
                self.empty = True
98
99
    def load_default_properties(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
100
        pass
101
102
    def load_from_issue(self, issue):
103
        '''
104
        Load the object with data from a generic issue
105
106
        Args:
107
            issue (class): The generic issue to load from
108
        '''
109
        pass
110
111
    def get_name(self):
112
        '''
113
        Get name for task juggler property
114
115
        Returns:
116
            str: Name of the task juggler property
117
        '''
118
        return self.name
119
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
120
    def get_id(self):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
121
        return ""
122
123
    def set_value(self, value):
124
        '''
125
        Set value for task juggler property
126
127
        Args:
128
            value (object): New value of the property
129
        '''
130
        self.value = value
131
132
    def append_value(self, value):
133
        '''
134
        Append value for task juggler property
135
136
        Args:
137
            value (object): Value to append to the property
138
        '''
139
        self.value.append(value)
140
141
    def get_value(self):
142
        '''
143
        Get value for task juggler property
144
145
        Returns:
146
            str: Value of the task juggler property
147
        '''
148
        if self.value == self.DEFAULT_VALUE: return ""
0 ignored issues
show
Coding Style introduced by
More than one statement on a single line
Loading history...
149
        return self.value
150
151
    def validate(self, task, tasks):
152
        '''
153
        Validate (and correct) the current task property
154
155
        Args:
156
            task (JugglerTask): Task to which the property belongs
157
            tasks (list):       List of JugglerTask's to which the current task belongs. Will be used to
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (104/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
158
                                verify relations to other tasks.
159
        '''
160
        pass
161
162
    def __str__(self):
163
        '''
164
        Convert task property object to the task juggler syntax
165
166
        Returns:
167
            str: String representation of the task property in juggler syntax
168
        '''
169
170
        if self.get_value(): 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
171
            # TODO: list support (like allocate multiple) (copy from taskdepends)
172
            # TODO: identifier conversion support?
173
            return self.TEMPLATE.format(prop=self.get_name(),
174
                                        value=self.VALUE_TEMPLATE.format(prefix=self.PREFIX,
175
                                                                         value=self.get_value(),
176
                                                                         suffix=self.SUFFIX))
177
        return ''
178
179
class JugglerTaskAllocate(JugglerTaskProperty):
180
    '''Class for the allocate (assignee) of a juggler task'''
181
182
    DEFAULT_NAME = 'allocate'
183
    DEFAULT_VALUE = 'not assigned'
184
185
    def load_from_issue(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
186
        '''
187
        Load the object with data from a generic issue
188
189
        Args:
190
            issue (class): The generic issue to load from
191
        '''
192
        self.set_value(self.DEFAULT_VALUE)
193
        if not issue is None:
194
            self.set_value(issue) # TODO: this may be list or primitive
195
196
class JugglerTaskPriority(JugglerTaskProperty):
197
    '''Class for task priority'''
198
    DEFAULT_NAME = "priority"
199
    DEFAULT_VALUE = 1000
200
201
class JugglerTaskStart(JugglerTaskProperty):
202
    DEFAULT_NAME = "start"
203
    DEFAULT_VALUE = ""
204
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
205
    def set_value(self, dt):
0 ignored issues
show
Bug introduced by
Parameters differ from overridden 'set_value' method
Loading history...
206
        if not dt: 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
207
            self.value = ""
208
            return
209
        if not isinstance(dt, datetime.datetime):
210
            raise ValueError("Task start value should be datetime object")
211
        self.value = dt
212
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
213
    def get_value(self):
214
        # TODO: fix API
215
        # WARNING: get_value returns tjp value
216
        if not self.value:
217
            return ""
218
        return to_tj3time(self.value)
219
220
class JugglerTaskEffort(JugglerTaskProperty):
221
    '''Class for the effort (estimate) of a juggler task'''
222
223
    #For converting the seconds (generic) to days
224
    UNIT = 'd'
225
    FACTOR = 8.0 * 60 * 60
226
227
    DEFAULT_NAME = 'effort'
228
    MINIMAL_VALUE = 1.0 / 8
229
    DEFAULT_VALUE = MINIMAL_VALUE
230
    SUFFIX = UNIT
231
232
    def load_default_properties(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
233
        self.SUFFIX = self.UNIT
234
235
    def load_from_issue(self, issue):
236
        '''
237
        Load the object with data from a generic issue
238
239
        Args:
240
            issue (class): The generic issue to load from
241
        '''
242
        self.set_value(self.DEFAULT_VALUE)
243
        if hasattr(issue.fields, 'aggregatetimeoriginalestimate') and issue.fields.aggregatetimeoriginalestimate:
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (113/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
244
            val = issue.fields.aggregatetimeoriginalestimate
245
            self.set_value(val / self.FACTOR)
246
        else:
247
            logging.warning('No estimate found for %s, assuming %s%s', issue.key, self.DEFAULT_VALUE, self.UNIT)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (112/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
248
    def set_value(self, value):
249
        '''
250
        Set the value for effort. Will convert whatever number to integer.
251
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
252
        Default class unit is 'days'. Can be overrided by setting "UNIT" global class attribute
253
        '''
254
        self.value = int(value)
255
    def validate(self, task, tasks):
256
        '''
257
        Validate (and correct) the current task property
258
259
        Args:
260
            task (JugglerTask): Task to which the property belongs
261
            tasks (list):       List of JugglerTask's to which the current task belongs. Will be used to
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (104/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
262
                                verify relations to other tasks.
263
        '''
264
        pass
265
        # if self.get_value() < self.MINIMAL_VALUE:
266
        #     logging.warning('Estimate %s%s too low for %s, assuming %s%s', self.get_value(), self.UNIT, task.key, self.MINIMAL_VALUE, self.UNIT)
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (146/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
267
        #     self.set_value(self.MINIMAL_VALUE)
268
269
class JugglerTaskDepends(JugglerTaskProperty):
270
    '''Class for the effort (estimate) of a juggler task'''
271
272
    DEFAULT_NAME = 'depends'
273
    DEFAULT_VALUE = []
274
    PREFIX = '!'
275
276
    def set_value(self, value):
277
        '''
278
        Set value for task juggler property (deep copy)
279
280
        Args:
281
            value (object): New value of the property
282
        '''
283
        self.value = list(value)
284
285
    def load_from_issue(self, issue):
286
        '''
287
        Load the object with data from a generic issue
288
289
        Args:
290
            issue (class): The generic issue to load from
291
        '''
292
        raise NotImplementedError("load_from_issue is not implemented for this depend")
293
        # TODO: remove these - are for jira
294
        self.set_value(self.DEFAULT_VALUE)
0 ignored issues
show
Unused Code introduced by
This code does not seem to be reachable.
Loading history...
295
        if hasattr(issue.fields, 'issuelinks'):
296
            for link in issue.fields.issuelinks:
297
                if hasattr(link, 'inwardIssue') and link.type.name == 'Blocker':
298
                    self.append_value(to_identifier(link.inwardIssue.key))
299
300
    def validate(self, task, tasks):
301
        '''
302
        Validate (and correct) the current task property
303
304
        Args:
305
            task (JugglerTask): Task to which the property belongs
306
            tasks (list):       List of JugglerTask's to which the current task belongs. Will be used to
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (104/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
307
                                verify relations to other tasks.
308
        '''
309
        # TODO: add support for nested tasks with self.parent
310
        for val in list(self.get_value()):
311
             if val not in [tsk.get_id() for tsk in tasks]:
0 ignored issues
show
Coding Style introduced by
The indentation here looks off. 12 spaces were expected, but 13 were found.
Loading history...
312
                 logging.warning('Removing link to %s for %s, as not within scope', val, task.get_id())
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (103/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Coding Style introduced by
The indentation here looks off. 16 spaces were expected, but 17 were found.
Loading history...
313
                 self.value.remove(val)
0 ignored issues
show
Coding Style introduced by
The indentation here looks off. 16 spaces were expected, but 17 were found.
Loading history...
314
315
    def __str__(self):
316
        '''
317
        Convert task property object to the task juggler syntax
318
319
        Returns:
320
            str: String representation of the task property in juggler syntax
321
        '''
322
323
        if self.get_value():
324
            valstr = ''
325
            for val in self.get_value():
326
                if valstr:
327
                    valstr += ', '
328
                valstr += self.VALUE_TEMPLATE.format(prefix=self.PREFIX,
329
                                                     value=to_identifier(val),
330
                                                     suffix=self.SUFFIX)
331
            return self.TEMPLATE.format(prop=self.get_name(),
332
                                        value=valstr)
333
        return ''
334
335
# class NonEmptyObject(object):
336
#     def __init__(self):
337
#         self.empty = False
338
#     def __bool__(self):
339
#         return not self.empty
340
#     __nonzero__=__bool__
341
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
342
343
class JugglerCompoundKeyword(object):
344
345
    '''Class for a general compound object in TJ syntax'''
346
347
    COMMENTS_HEADER = ""
348
    LOG_STRING = "DefaultKeyword"
349
    DEFAULT_KEYWORD = 'unknown_keyword'
350
    DEFAULT_ID = "" # id may be empty for some keywords
351
    DEFAULT_SUMMARY = '' # no summary is possible everywhere
352
    TEMPLATE = '''{header}\n{keyword} {id}'''
353
    ENCLOSED_BLOCK = True
354
355
    def __init__(self, issue=None):
356
        logging.info('Create %s', self.LOG_STRING)
357
        self.empty = False
358
        self.parent = None
359
        self.keyword = self.DEFAULT_KEYWORD
360
        self.id = self.DEFAULT_ID
361
        self.summary = self.DEFAULT_SUMMARY
362
        self.option2 = ""
363
        self.properties = OrderedDict()
364
        self.load_default_properties(issue)
365
366
        if issue:
367
            if self.load_from_issue(issue) is False:
368
                self.empty = True
369
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
370
    def load_from_issue(self, issue):
371
        '''
372
        Load the object with data from a generic issue
373
374
        Args:
375
            issue (class): The generic issue to load from
376
        '''
377
        pass
378
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
379
    def load_default_properties(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
380
        pass
381
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
382
    def get_name(self):
383
        return self.keyword
384
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
385
    def get_id(self):
386
        return self.id
387
388
    def set_property(self, prop):
389
        if prop: 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
390
            self.properties[prop.get_name() + repr(prop.get_id())] = prop
391
            prop.parent = self # TODO: control un-set?, GC?
392
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
393
    def set_id(self, id):
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
394
        self.id = id
395
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
396
    def decode(self):
397
        return self.option2
398
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
399
    def walk(self, cls, ls = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
400
        if ls is None: ls = []
0 ignored issues
show
Coding Style introduced by
More than one statement on a single line
Loading history...
401
        for key, item in self.properties.items():
0 ignored issues
show
Unused Code introduced by
The variable key seems to be unused.
Loading history...
402
            if isinstance(item, JugglerCompoundKeyword):
403
                ls = item.walk(cls, ls)
404
            if isinstance(item, cls):
405
                ls.append(item)
406
        return ls
407
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
408
    def __str__(self):
409
        if self.empty: return ""
0 ignored issues
show
Coding Style introduced by
More than one statement on a single line
Loading history...
410
        out = self.TEMPLATE.format(header=self.COMMENTS_HEADER,keyword=self.keyword, id=to_identifier(self.id))
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (111/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Coding Style introduced by
Exactly one space required after comma
Loading history...
411
        if self.summary:
412
            out += ' "%s"' % self.summary.replace('\"', '\\\"')
413
        if self.option2:
414
            out += ' %s ' % self.option2
415
        if self.properties and self.ENCLOSED_BLOCK: out += " {\n"
0 ignored issues
show
Coding Style introduced by
More than one statement on a single line
Loading history...
416
        for prop in self.properties:
417
            out += str(self.properties[prop])
418
        if self.properties and self.ENCLOSED_BLOCK: out += "\n}"
0 ignored issues
show
Coding Style introduced by
More than one statement on a single line
Loading history...
419
        return out
420
421
class JugglerSimpleProperty(JugglerCompoundKeyword):
422
    LOG_STRING = "Default Simple Property"
423
    DEFAULT_NAME = 'unknown_property'
424
    DEFAULT_VALUE = ''
425
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
426
    def load_default_properties(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
427
        self.keyword = self.DEFAULT_NAME
428
        self.set_value(self.DEFAULT_VALUE)
429
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
430
    def load_from_issue(self, value):
0 ignored issues
show
Bug introduced by
Parameters differ from overridden 'load_from_issue' method
Loading history...
431
        self.set_value(value)
432
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
433
    def get_name(self):
434
        return self.keyword
435
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
436
    def decode(self):
437
        return self.id
438
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
439
    def get_value(self):
440
        return self.id
441
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
442
    def set_value(self, val):
443
        if val or val == 0: self.id = repr(val).replace("'",'"')
0 ignored issues
show
Coding Style introduced by
Exactly one space required after comma
Loading history...
Coding Style introduced by
More than one statement on a single line
Loading history...
444
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
445
class JugglerTimezone(JugglerSimpleProperty):
446
    DEFAULT_NAME = 'timezone'
447
    DEFAULT_VALUE = 'Europe/Dublin'
448
449
class JugglerOutputdir(JugglerSimpleProperty):
450
    LOG_STRING = "outputdir property"
451
    DEFAULT_NAME = 'outputdir'
452
    DEFAULT_VALUE = 'REPORT'
453
    # TODO HERE: need to create the outputdir folder for this to execute!
454
455
class JugglerIcalreport(JugglerSimpleProperty):
456
    LOG_STRING = "icalreport property"
457
    DEFAULT_NAME = 'icalreport'
458
    DEFAULT_VALUE = 'calendar'
459
460
class JugglerResource(JugglerCompoundKeyword):
461
    DEFAULT_KEYWORD = "resource"
462
    DEFAULT_ID = "me"
463
    DEFAULT_SUMMARY = "Default Resource"
464
465
class JugglerTask(JugglerCompoundKeyword):
466
467
    '''Class for a task for Task-Juggler'''
468
469
    LOG_STRING = "JugglerTask"
470
    DEFAULT_KEYWORD = 'task'
471
    DEFAULT_ID = "unknown_task"
472
    DEFAULT_SUMMARY = 'Task is not initialized'
473
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
474
    # def load_default_properties(self, issue):
475
    #     self.set_property(JugglerTaskAllocate(issue))
476
    #     self.set_property(JugglerTaskEffort(issue))
477
    #     self.set_property(JugglerTaskDepends(issue))
478
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
479
    # def load_from_issue(self, issue):
480
    #     '''
481
    #     Load the object with data from a generic issue
482
483
    #     Args:
484
    #         issue (?): The generic issue to load from
485
    #     '''
486
    #     self.id = hash(issue)
487
    #     self.load_default_properties(issue)
488
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
489
    def validate(self, tasks):
490
        '''
491
        Validate (and correct) the current task
492
493
        Args:
494
            tasks (list): List of JugglerTask's to which the current task belongs. Will be used to
495
                          verify relations to other tasks.
496
        '''
497
        if self.id == self.DEFAULT_ID:
498
            logging.error('Found a task which is not initialized')
499
500
        for prop in self.properties:
501
            self.properties[prop].validate(self, tasks)
502
503
    # def __str__(self):
504
    #     '''
505
    #     Convert task object to the task juggler syntax
506
507
    #     Returns:
508
    #         str: String representation of the task in juggler syntax
509
    #     '''
510
    #     props = ''
511
    #     for prop in self.properties:
512
    #         props += str(self.properties[prop])
513
    #     return self.TEMPLATE.format(id=to_identifier(self.key),
514
    #                                 key=self.key,
515
    #                                 description=self.summary.replace('\"', '\\\"'),
516
    #                                 props=props)
517
518
class JugglerTimesheet():
519
    pass
520
521
class JugglerBooking(JugglerCompoundKeyword):
522
    LOG_STRING = "JugglerBooking"
523
    DEFAULT_KEYWORD = "booking"
524
    DEFAULT_ID = "me" # resource
525
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
526
    def load_default_properties(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
527
        self.start = None
528
        self.end = None
529
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
530
    def set_resource(self, res):
531
        self.set_id(res)
532
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
533
    def set_interval(self, start, end):
534
        self.start = start
0 ignored issues
show
Coding Style introduced by
The attribute start was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
535
        self.end = end
0 ignored issues
show
Coding Style introduced by
The attribute end was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
536
        self.option2 = to_tj3interval(self.start, self.end)
537
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
538
    def decode(self):
539
        return [self.start, self.end]
540
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
541
    def load_from_issue(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
542
        start = issue["start"]
543
        end = issue["end"]
544
        self.set_interval(start, end)
545
        self.set_resource(issue["resource"])
546
547
class JugglerProject(JugglerCompoundKeyword):
548
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
549
    '''Template for TaskJuggler project'''
550
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
551
    LOG_STRING = "JugglerProject"
552
    DEFAULT_KEYWORD = 'project'
553
    DEFAULT_ID = "default" # id may be empty for some keywords
554
    DEFAULT_SUMMARY = 'Default Project' # no summary is possible everywhere
555
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
556
    def load_default_properties(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
557
        self.set_property(JugglerTimezone())
558
        self.set_property(JugglerOutputdir())
559
        self.set_interval()
560
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
561
    def set_interval(self, start = datetime.datetime.now().replace(microsecond=0,second=0,minute=0), end = datetime.datetime(2035, 1, 1)):
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (138/100).

This check looks for lines that are too long. You can specify the maximum line length.

Loading history...
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
Coding Style introduced by
Exactly one space required after comma
Loading history...
562
        self.option2 = to_tj3interval(start, end)
563
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
564
565
class JugglerSource(JugglerCompoundKeyword):
566
    """
567
    The entire project skeleton
568
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
569
    Sets reports and folders for use with parser
570
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
571
    Must be extended with load_from_issue(self,issue) appending tasks 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
572
    """
573
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
574
    LOG_STRING = "JugglerSource"
575
    DEFAULT_KEYWORD = ''
576
    DEFAULT_ID = ''
577
    COMMENTS_HEADER = '''
578
    // TaskJuggler 3 source
579
    // generated by python-juggler (c) 2017 Andrew Gryaznov and others
580
    // https://github.com/grandrew/taskjuggler-python
581
    '''
582
    ENCLOSED_BLOCK = False
583
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
584
    def load_default_properties(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
585
        self.set_property(JugglerProject())
586
        self.set_property(JugglerResource())
587
        self.set_property(JugglerIcalreport())
588
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
589
class GenericJuggler(object):
590
591
    '''Class for task-juggling generic results'''
592
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
593
    src = None
594
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
595
    def __init__(self):
596
        '''
597
        Construct a generic juggler object
598
599
        Args:
600
            none
601
        '''
602
603
        logging.info('generic load')
604
605
    def set_query(self, query):
606
        '''
607
        Set the query for the generic juggler object
608
609
        Args:
610
            query (str): The Query to run on generic server
611
        '''
612
613
        logging.info('Query: %s', query)
614
        self.query = query
0 ignored issues
show
Coding Style introduced by
The attribute query was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
615
        self.issue_count = 0
0 ignored issues
show
Coding Style introduced by
The attribute issue_count was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
616
617
    @staticmethod
618
    def validate_tasks(tasks):
619
        '''
620
        Validate (and correct) tasks
621
622
        Args:
623
            tasks (list): List of JugglerTask's to validate
624
        '''
625
        for task in tasks:
626
            task.validate(tasks)
627
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
628
    def load_issues(self):
629
        raise NotImplementedError
630
631
    def load_issues_incremetal(self):
632
        if self.loaded: return []
0 ignored issues
show
Coding Style introduced by
More than one statement on a single line
Loading history...
633
        self.loaded = True
0 ignored issues
show
Coding Style introduced by
The attribute loaded was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
634
        return self.load_issues()
635
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
636
    def create_task_instance(self, issue):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
637
        return JugglerTask(issue)
638
639
    def load_issues_from_generic(self):
640
        '''
641
        Load issues from generic
642
643
        Returns:
644
            list: A list of dicts containing the generic tickets
645
        '''
646
        tasks = []
647
        busy = True
648
        self.loaded = False
0 ignored issues
show
Coding Style introduced by
The attribute loaded was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
649
        self.issue_count = 0
0 ignored issues
show
Coding Style introduced by
The attribute issue_count was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
650
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
651
        while busy:
652
            try:
653
                issues = self.load_issues_incremetal()
654
            except NotImplementedError:
655
                logging.error('Loading Issues is not implemented in upstream library')
656
                return None
657
                
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
658
            except:
0 ignored issues
show
Coding Style Best Practice introduced by
General except handlers without types should be used sparingly.

Typically, you would use general except handlers when you intend to specifically handle all types of errors, f.e. when logging. Otherwise, such general error handlers can mask errors in your application that you want to know of.

Loading history...
659
                logging.error('Could not get issue')
660
                return None
661
662
            if len(issues) <= 0:
663
                busy = False
664
665
            self.issue_count += len(issues)
666
667
            for issue in issues:
668
                logging.debug('Retrieved %s', repr(issue))
669
                tasks.append(self.create_task_instance(issue))
670
671
        self.validate_tasks(tasks)
672
673
        return tasks
674
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
675
    def create_jugglersource_instance(self):
0 ignored issues
show
Coding Style introduced by
This method could be written as a function/class method.

If a method does not access any attributes of the class, it could also be implemented as a function or static method. This can help improve readability. For example

class Foo:
    def some_method(self, x, y):
        return x + y;

could be written as

class Foo:
    @classmethod
    def some_method(cls, x, y):
        return x + y;
Loading history...
676
        return JugglerSource()
677
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
678
    def juggle(self):
679
        """
680
        Export the loaded issues onto the JuggleSource structure
681
        """
682
        issues = self.load_issues_from_generic()
683
        if not issues:
684
            return None
685
        # TODO HERE set reportfolder from parameters
686
        self.src = self.create_jugglersource_instance()
687
        for issue in issues:
688
            self.src.set_property(issue)
689
        return self.src
690
691
    def write_file(self, output=None):
692
        '''
693
        Query generic and generate task-juggler output from given issues
694
695
        Args:
696
            output (str): Name of output file, for task-juggler
697
        '''
698
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
699
        if not self.src:
700
            self.juggle()
701
            
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
702
        s = str(self.src)
703
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
704
        if output and isinstance(output, str):
705
            with open(output, 'w') as out:
706
                out.write(s)
707
        elif output and isinstance(output, file):
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'file'
Loading history...
708
            output.write(s)
709
        # else:
710
        #     raise ValueError("output should be a filename string or a file handler")
711
        return s
712
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
713
    def read_ical_result(self, icalfile):
714
        tasks = self.src.walk(JugglerTask)
715
        cal = icalendar.Calendar.from_ical(file(icalfile).read())
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'file'
Loading history...
716
        for ev in cal.walk('VEVENT'): # pylint:disable=no-member
717
            start_date = ev.decoded("DTSTART")
718
            end_date = ev.decoded("DTEND")
719
            id = from_identifier(ev.decoded("UID").split("-")[1])
0 ignored issues
show
Bug Best Practice introduced by
This seems to re-define the built-in id.

It is generally discouraged to redefine built-ins as this makes code very hard to read.

Loading history...
720
            for t in tasks:
721
                if t.id == id:
722
                    # logging.info("Testing %s" % str(t))
723
                    # logging.info("Got %s" % repr(t.walk(JugglerTaskAllocate)))
724
                    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
725
                    # ical does not support resource allocation reporting
726
                    # so we do not support multiple resource here
727
                    # and we don't support them yet anyways
728
                    t.set_property(JugglerBooking({
729
                        "resource":t.walk(JugglerTaskAllocate)[0].get_value(),
730
                        "start": start_date,
731
                        "end": end_date
732
                        }))
733
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
734
    def run(self, outfolder=None, infile=None):
735
        '''
736
        Run the taskjuggler task
737
738
        Args:
739
            output (str): Name of output file, for task-juggler
740
        '''
741
        if not self.src:
742
            self.juggle()
743
            
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
744
        if outfolder is None:
745
            outfolder = tempfile.mkdtemp("TJP")
746
        self.outfolder = outfolder
0 ignored issues
show
Coding Style introduced by
The attribute outfolder was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
747
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
748
        if infile is None:
749
            infile = tempfile.mkstemp(".tjp")[1]
750
        self.infile = infile
0 ignored issues
show
Coding Style introduced by
The attribute infile was defined outside __init__.

It is generally a good practice to initialize all attributes to default values in the __init__ method:

class Foo:
    def __init__(self, x=None):
        self.x = x
Loading history...
751
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
752
        reportdir = self.src.walk(JugglerOutputdir)
753
        orig_rep = reportdir[0].get_value()
754
        reportdir[0].set_value(outfolder)
755
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
756
        ical_report_name = "calendar_out"
757
        ical_report_path = os.path.join(outfolder, ical_report_name)
758
        icalreport = self.src.walk(JugglerIcalreport)
759
        orig_cal = icalreport[0].get_value()
760
        icalreport[0].set_value(ical_report_name)
761
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
762
        self.write_file(infile)
763
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
764
        subprocess.call(["/usr/bin/env", "tj3", infile])
765
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
766
        self.read_ical_result(ical_report_path+".ics")
767
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
768
        # shutil.rmtree(self.outfolder)
769
        # os.remove(self.infile)
770
        icalreport[0].set_value(orig_cal)
771
        reportdir[0].set_value(orig_rep)
772
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
773
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
774
        # TODO HERE: load the ical file back to the actual tree (no tree support yet?)
775
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
776
    def clean(self):
777
        "clean after running"
778
        # try: shutil.rmtree(self.outfolder)
779
        # except:  pass
780
        # try: os.remove(self.infile)
781
        # except: pass
782
        raise NotImplementedError
783
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
784
    def walk(self, cls):
785
        if not self.src:
786
            self.juggle()
787
        return self.src.walk(cls)
788
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
789
    def __inter__(self):
790
        "provide dict(j) method to generate dictionary structure for tasks"
791
        raise NotImplementedError
792
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
793
    def __del__(self):
794
        self.clean()
795
796
0 ignored issues
show
coding-style introduced by
Trailing newlines
Loading history...
797