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 ( ff6a96...dcc5a2 )
by Andrew
01:14
created

JugglerSource._post_init()   A

Complexity

Conditions 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
dl 0
loc 2
rs 10
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
standard import "import logging, tempfile, subprocess, datetime, icalendar, shutil, os" should be placed before "import logging, tempfile, subprocess, datetime, icalendar, shutil, os"
Loading history...
10
from collections import OrderedDict
0 ignored issues
show
introduced by
standard import "from collections import OrderedDict" should be placed before "import logging, tempfile, subprocess, datetime, icalendar, shutil, os"
Loading history...
11
12
DEFAULT_LOGLEVEL = 'warning'
13
DEFAULT_OUTPUT = 'export.tjp'
14
15
TAB = ' ' * 4
16
17
DEBUG = False
18
19
def is_number(s):
20
    try:
21
        float(s)
22
        return True
23
    except ValueError:
24
        return False
25
26
def set_logging_level(loglevel):
27
    '''
28
    Set the logging level
29
30
    Args:
31
        loglevel String representation of the loglevel
32
    '''
33
    numeric_level = getattr(logging, loglevel.upper(), None)
34
    if not isinstance(numeric_level, int):
35
        raise ValueError('Invalid log level: %s' % loglevel)
36
    logging.getLogger().setLevel(numeric_level)
37
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
38
def to_tj3time(dt):
39
    """
40
    Convert python date or datetime object to TJ3 format
41
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
42
    """
43
    return dt.isoformat().replace("T", "-").split(".")[0].split("+")[0]
44
45
def to_tj3interval(start, end):
46
    return "%s - %s" % (to_tj3time(start), to_tj3time(end))
47
48
TJP_NUM_ID_PREFIX = "tjp_numid_"
49
TJP_DASH_PREFIX = "__DASH__"
50
TJP_SPACE_PREFIX = "__SPACE__"
51
52
def to_identifier(key):
53
    '''
54
    Convert given key to identifier, interpretable by TaskJuggler as a task-identifier
55
56
    Args:
57
        key (str): Key to be converted
58
59
    Returns:
60
        str: Valid task-identifier based on given key
61
    '''
62
    if is_number(key):
63
        key = TJP_NUM_ID_PREFIX+str(key)
64
    key = key.replace('-', TJP_DASH_PREFIX).replace(" ", TJP_SPACE_PREFIX)
65
    return key
66
67
def from_identifier(key):
68
    if TJP_NUM_ID_PREFIX in key:
69
        return int(key.replace(TJP_NUM_ID_PREFIX, ""))
70
    return key.replace(TJP_DASH_PREFIX, "-").replace(TJP_SPACE_PREFIX, " ")
71
72
class JugglerTaskProperty(object):
73
    '''Class for a property of a Task Juggler'''
74
75
    DEFAULT_NAME = 'property name'
76
    DEFAULT_VALUE = 'not initialized'
77
    PREFIX = ''
78
    SUFFIX = ''
79
    TEMPLATE = TAB + '{prop} {value}\n'
80
    VALUE_TEMPLATE = '{prefix}{value}{suffix}'
81
    LOG_STRING = "Default TaskProperty"
82
83
84
    def __init__(self, issue=None):
85
        '''
86
        Initialize task juggler property
87
88
        Args:
89
            issue (class): The generic issue to load from
90
            value (object): Value of the property
91
        '''
92
        logging.debug('Create %s', self.LOG_STRING)
93
        self.name = self.DEFAULT_NAME
94
        self.set_value(self.DEFAULT_VALUE)
95
        self.empty = False
96
        self.parent = None
97
        self.top = None
98
        self.load_default_properties(issue)
99
100
        if issue:
101
            if self.load_from_issue(issue) is False:
102
                self.empty = True
103
104
    def load_default_properties(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
105
        pass
106
107
    def load_from_issue(self, issue):
108
        '''
109
        Load the object with data from a generic issue
110
111
        Args:
112
            issue (class): The generic issue to load from
113
        '''
114
        pass
115
116
    def get_name(self):
117
        '''
118
        Get name for task juggler property
119
120
        Returns:
121
            str: Name of the task juggler property
122
        '''
123
        return self.name
124
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
125
    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...
126
        return ""
127
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
128
    def get_hash(self):
129
        return self.get_name() + repr(self.get_value())
130
131
    def set_value(self, value):
132
        '''
133
        Set value for task juggler property
134
135
        Args:
136
            value (object): New value of the property
137
        '''
138
        self.value = value
139
140
    def append_value(self, value):
141
        '''
142
        Append value for task juggler property
143
144
        Args:
145
            value (object): Value to append to the property
146
        '''
147
        self.value.append(value)
148
149
    def get_value(self):
150
        '''
151
        Get value for task juggler property
152
153
        Returns:
154
            str: Value of the task juggler property
155
        '''
156
        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...
157
        return self.value
158
159
    def validate(self, task, tasks):
160
        '''
161
        Validate (and correct) the current task property
162
163
        Args:
164
            task (JugglerTask): Task to which the property belongs
165
            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...
166
                                verify relations to other tasks.
167
        '''
168
        pass
169
170
    def __str__(self):
171
        '''
172
        Convert task property object to the task juggler syntax
173
174
        Returns:
175
            str: String representation of the task property in juggler syntax
176
        '''
177
178
        if self.get_value(): 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
179
            # TODO: list support (like allocate multiple) (copy from taskdepends)
180
            # TODO: identifier conversion support?
181
            return self.TEMPLATE.format(prop=self.get_name(),
182
                                        value=self.VALUE_TEMPLATE.format(prefix=self.PREFIX,
183
                                                                         value=self.get_value(),
184
                                                                         suffix=self.SUFFIX))
185
        return ''
186
187
class JugglerTaskAllocate(JugglerTaskProperty):
188
    '''Class for the allocate (assignee) of a juggler task'''
189
190
    DEFAULT_NAME = 'allocate'
191
    DEFAULT_VALUE = 'not initialized'
192
193
    def load_from_issue(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
194
        '''
195
        Load the object with data from a generic issue
196
197
        Args:
198
            issue (class): The generic issue to load from
199
        '''
200
        self.set_value(self.DEFAULT_VALUE)
201
        if not issue is None:
202
            self.set_value(issue) # TODO: this may be list or primitive
203
204
class JugglerTaskPriority(JugglerTaskProperty):
205
    '''Class for task priority'''
206
    LOG_STRING = "JugglerTaskPriority"
207
    DEFAULT_NAME = "priority"
208
    DEFAULT_VALUE = 1000
209
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
210
    def get_hash(self):
211
        return self.get_name()
212
213
class JugglerTaskStart(JugglerTaskProperty):
214
    LOG_STRING = "JugglerTaskStart"
215
    DEFAULT_NAME = "start"
216
    DEFAULT_VALUE = ""
217
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
218
    def set_value(self, dt):
0 ignored issues
show
Bug introduced by
Parameters differ from overridden 'set_value' method
Loading history...
219
        if not dt: 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
220
            self.value = ""
221
            return
222
        if not isinstance(dt, datetime.datetime):
223
            raise ValueError("Task start value should be datetime object")
224
        self.value = dt
225
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
226
    def get_value(self):
227
        # TODO: fix API
228
        # WARNING: get_value returns tjp value
229
        if not self.value:
230
            return ""
231
        return to_tj3time(self.value)
232
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
233
    def get_hash(self):
234
        return self.get_name()
235
236
class JugglerTaskEffort(JugglerTaskProperty):
237
    '''Class for the effort (estimate) of a juggler task'''
238
239
    #For converting the seconds (generic) to days
240
    UNIT = 'h'
241
242
    DEFAULT_NAME = 'effort'
243
    MINIMAL_VALUE = 1 # TODO: should be project resolution, add check
244
    DEFAULT_VALUE = -1 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
245
    SUFFIX = UNIT
246
247
    def load_default_properties(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
248
        self.SUFFIX = self.UNIT
249
250
    def load_from_issue(self, issue):
251
        '''
252
        Load the object with data from a generic issue
253
254
        Args:
255
            issue (class): The generic issue to load from
256
        '''
257
        if issue: self.set_value(issue)
0 ignored issues
show
Coding Style introduced by
More than one statement on a single line
Loading history...
258
        else: self.set_value(self.DEFAULT_VALUE)
259
    def set_value(self, value):
260
        '''
261
        Set the value for effort. Will convert whatever number to integer.
262
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
263
        Default class unit is 'days'. Can be overrided by setting "UNIT" global class attribute
264
        '''
265
        self.value = int(value) # TODO: support for FP?
266
    def get_hash(self):
267
        return self.get_name()
268
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
269
    def decode(self):
270
        return self.value # only hours supported yet
271
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
272
    def validate(self, task, tasks):
273
        '''
274
        Validate (and correct) the current task property
275
276
        Args:
277
            task (JugglerTask): Task to which the property belongs
278
            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...
279
                                verify relations to other tasks.
280
        '''
281
        pass
282
        # if self.get_value() < self.MINIMAL_VALUE:
283
        #     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...
284
        #     self.set_value(self.MINIMAL_VALUE)
285
286
class JugglerTaskDepends(JugglerTaskProperty):
287
    '''Class for the effort (estimate) of a juggler task'''
288
289
    DEFAULT_NAME = 'depends'
290
    DEFAULT_VALUE = []
291
    PREFIX = '!'
292
293
    def set_value(self, value):
294
        '''
295
        Set value for task juggler property (deep copy)
296
297
        Args:
298
            value (object): New value of the property
299
        '''
300
        self.value = list(value)
301
302
    def load_from_issue(self, issue):
303
        '''
304
        Load the object with data from a generic issue
305
306
        Args:
307
            issue (class): The generic issue to load from
308
        '''
309
        raise NotImplementedError("load_from_issue is not implemented for this depend")
310
        # TODO: remove these - are for jira
311
        self.set_value(self.DEFAULT_VALUE)
0 ignored issues
show
Unused Code introduced by
This code does not seem to be reachable.
Loading history...
312
        if hasattr(issue.fields, 'issuelinks'):
313
            for link in issue.fields.issuelinks:
314
                if hasattr(link, 'inwardIssue') and link.type.name == 'Blocker':
315
                    self.append_value(to_identifier(link.inwardIssue.key))
316
317
    def validate(self, task, tasks):
318
        '''
319
        Validate (and correct) the current task property
320
321
        Args:
322
            task (JugglerTask): Task to which the property belongs
323
            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...
324
                                verify relations to other tasks.
325
        '''
326
        # TODO: add support for nested tasks with self.parent
327
        for val in list(self.get_value()):
328
             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...
329
                 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...
330
                 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...
331
332
    def __str__(self):
333
        '''
334
        Convert task property object to the task juggler syntax
335
336
        Returns:
337
            str: String representation of the task property in juggler syntax
338
        '''
339
340
        if self.get_value():
341
            valstr = ''
342
            for val in self.get_value():
343
                if valstr:
344
                    valstr += ', '
345
                valstr += self.VALUE_TEMPLATE.format(prefix=self.PREFIX,
346
                                                     value=to_identifier(val),
347
                                                     suffix=self.SUFFIX)
348
            return self.TEMPLATE.format(prop=self.get_name(),
349
                                        value=valstr)
350
        return ''
351
352
# class NonEmptyObject(object):
353
#     def __init__(self):
354
#         self.empty = False
355
#     def __bool__(self):
356
#         return not self.empty
357
#     __nonzero__=__bool__
358
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
359
360
class JugglerCompoundKeyword(object):
0 ignored issues
show
best-practice introduced by
Too many instance attributes (8/7)
Loading history...
361
362
    '''Class for a general compound object in TJ syntax'''
363
364
    COMMENTS_HEADER = ""
365
    LOG_STRING = "DefaultKeyword"
366
    DEFAULT_KEYWORD = 'unknown_keyword'
367
    DEFAULT_ID = "" # id may be empty for some keywords
368
    DEFAULT_SUMMARY = '' # no summary is possible everywhere
369
    TEMPLATE = '''{header}\n{keyword} {id}'''
370
    ENCLOSED_BLOCK = True
371
372
    def __init__(self, issue=None):
373
        logging.debug('Create %s', self.LOG_STRING)
374
        self.empty = False
375
        self.parent = None
376
        self.top = None
377
        self.keyword = self.DEFAULT_KEYWORD
378
        self.id = self.DEFAULT_ID
379
        self.summary = self.DEFAULT_SUMMARY
380
        self.option2 = ""
381
        self.properties = OrderedDict()
382
        self.load_default_properties(issue)
383
        self._post_init(issue)
384
385
        if issue:
386
            if self.load_from_issue(issue) is False:
387
                self.empty = True
388
                
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
389
    def _post_init(self, issue):
390
        pass
391
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
392
    def load_from_issue(self, issue):
393
        '''
394
        Load the object with data from a generic issue
395
396
        Args:
397
            issue (class): The generic issue to load from
398
        '''
399
        pass
400
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
401
    def load_default_properties(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
402
        pass
403
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
404
    def get_name(self):
405
        return self.keyword
406
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
407
    def get_id(self):
408
        return self.id
409
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
410
    def get_hash(self):
411
        """Used to generate unique hash. 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
412
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
413
        If set_property should replace this (only single property of this type is supported) - 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
414
            - the hash should only return the keyword
415
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
416
        By default, multiple compound properties are allowed.
417
        """
418
        return self.get_name() + repr(self.get_id())
419
420
    def set_property(self, prop):
421
        if prop: 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
422
            self.properties[prop.get_hash()] = prop
423
            prop.parent = self # TODO: control un-set?, GC?
424
            prop.top = self.top
425
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
426
    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...
427
        self.id = id
428
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
429
    def decode(self):
430
        return self.option2
431
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
432
    def walk(self, cls, ls = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
433
        if ls is None: 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
434
            ls = []
435
            if isinstance(self, cls):
436
                ls.append(self)
437
        for key, item in self.properties.items():
0 ignored issues
show
Unused Code introduced by
The variable key seems to be unused.
Loading history...
438
            if isinstance(item, JugglerCompoundKeyword):
439
                ls = item.walk(cls, ls)
440
            if isinstance(item, cls):
441
                ls.append(item)
442
        return ls
443
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
444
    def __str__(self):
445
        if self.empty: return ""
0 ignored issues
show
Coding Style introduced by
More than one statement on a single line
Loading history...
446
        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...
447
        if self.summary:
448
            out += ' "%s"' % self.summary.replace('\"', '\\\"')
449
        if self.option2:
450
            out += ' %s ' % self.option2
451
        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...
452
        for prop in self.properties:
453
            out += str(self.properties[prop])
454
        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...
455
        return out
456
457
class JugglerSimpleProperty(JugglerCompoundKeyword):
458
    """By default only one simple property is allowed."""
459
    LOG_STRING = "Default Simple Property"
460
    DEFAULT_NAME = 'unknown_property'
461
    DEFAULT_VALUE = ''
462
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
463
    def load_default_properties(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
464
        self.keyword = self.DEFAULT_NAME
465
        self.set_value(self.DEFAULT_VALUE)
466
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
467
    def load_from_issue(self, value):
0 ignored issues
show
Bug introduced by
Parameters differ from overridden 'load_from_issue' method
Loading history...
468
        self.set_value(value)
469
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
470
    def get_name(self):
471
        return self.keyword
472
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
473
    def get_hash(self):
474
        return self.get_name()
475
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
476
    def decode(self):
477
        return self.id
478
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
479
    def get_value(self):
480
        return self.id
481
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
482
    def set_value(self, val):
483
        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...
484
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
485
class JugglerTimezone(JugglerSimpleProperty):
486
    '''
487
    Sets the project timezone.
488
    Default value is UTC.
489
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
490
    Supports all tzdata values, see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
491
    or https://stackoverflow.com/q/13866926
492
    '''
493
    DEFAULT_NAME = 'timezone'
494
    DEFAULT_VALUE = 'UTC'
495
    # DEFAULT_VALUE = 'Europe/Dublin'
496
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
497
    # TODO: checks!
498
499
class JugglerOutputdir(JugglerSimpleProperty):
500
    LOG_STRING = "outputdir property"
501
    DEFAULT_NAME = 'outputdir'
502
    DEFAULT_VALUE = 'REPORT'
503
    # TODO HERE: need to create the outputdir folder for this to execute!
504
505
class JugglerIcalreport(JugglerSimpleProperty):
506
    LOG_STRING = "icalreport property"
507
    DEFAULT_NAME = 'icalreport'
508
    DEFAULT_VALUE = 'calendar'
509
510
class JugglerResource(JugglerCompoundKeyword):
511
    DEFAULT_KEYWORD = "resource"
512
    DEFAULT_ID = "me"
513
    DEFAULT_SUMMARY = "Default Resource"
514
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
515
    def set_value(self, value):
516
        self.summary = value
517
518
class JugglerWorkingHours(JugglerCompoundKeyword):
519
    DEFAULT_KEYWORD = "workinghours"
520
    DEFAULT_ID = "mon"
521
    DEFAULT_SUMMARY = ""
522
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
523
    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...
524
        if not id in ["mon", "tue", "wed", "thu", "fri", "sat", "sun"]: raise ValueError('id for workinghours must be one of: "mon", "tue", "wed", "thu", "fri", "sat", "sun"')
0 ignored issues
show
Coding Style introduced by
This line is too long as per the coding-style (175/100).

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

Loading history...
Coding Style introduced by
More than one statement on a single line
Loading history...
525
        self.id = id
526
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
527
    def set_weekday(self, d = "mon"):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
528
        """
529
        Set week day for workinghours
530
        "mon", "tue", "wed", "thu", "fri", "sat", "sun"
531
        """
532
        self.set_id(d)
533
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
534
    def load_default_properties(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
535
        self.set_hour_interval()
536
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
537
    def set_hour_interval(self, start = 9, end = 19):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
538
        self.option2 = "%s:00 - %s:00" % (start, end)
539
540
class JugglerTask(JugglerCompoundKeyword):
541
542
    '''Class for a task for Task-Juggler'''
543
544
    LOG_STRING = "JugglerTask"
545
    DEFAULT_KEYWORD = 'task'
546
    DEFAULT_ID = "unknown_task"
547
    DEFAULT_SUMMARY = 'Task is not initialized'
548
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
549
    def load_default_properties(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
550
        if not issue:
551
            self.set_property(JugglerTaskAllocate("me"))
552
            self.set_property(JugglerTaskEffort(1))
553
        else:
554
            self.set_property(JugglerTaskAllocate(issue))
555
            self.set_property(JugglerTaskEffort(issue))
556
            self.set_property(JugglerTaskDepends(issue))
557
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
558
    def load_from_issue(self, issue):
559
        '''
560
        Load the object with data from a generic issue
561
562
        Args:
563
            issue (?): The generic issue to load from
564
        '''
565
        self.load_default_properties(issue)
566
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
567
    def validate(self, tasks):
568
        '''
569
        Validate (and correct) the current task
570
571
        Args:
572
            tasks (list): List of JugglerTask's to which the current task belongs. Will be used to
573
                          verify relations to other tasks.
574
        '''
575
        if self.id == self.DEFAULT_ID:
576
            logging.error('Found a task which is not initialized')
577
578
        for prop in self.properties:
579
            self.properties[prop].validate(self, tasks)
580
581
    # def __str__(self):
582
    #     '''
583
    #     Convert task object to the task juggler syntax
584
585
    #     Returns:
586
    #         str: String representation of the task in juggler syntax
587
    #     '''
588
    #     props = ''
589
    #     for prop in self.properties:
590
    #         props += str(self.properties[prop])
591
    #     return self.TEMPLATE.format(id=to_identifier(self.key),
592
    #                                 key=self.key,
593
    #                                 description=self.summary.replace('\"', '\\\"'),
594
    #                                 props=props)
595
596
class JugglerTimesheet():
597
    pass
598
599
class JugglerBooking(JugglerCompoundKeyword):
600
    LOG_STRING = "JugglerBooking"
601
    DEFAULT_KEYWORD = "booking"
602
    DEFAULT_ID = "me" # resource
603
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
604
    def load_default_properties(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
605
        self.start = None
606
        self.end = None
607
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
608
    def set_resource(self, res):
609
        self.set_id(res)
610
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
611
    def set_interval(self, start, end):
612
        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...
613
        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...
614
        self.option2 = to_tj3interval(self.start, self.end)
615
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
616
    def decode(self):
617
        return [self.start, self.end]
618
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
619
    def load_from_issue(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
620
        start = issue["start"]
621
        end = issue["end"]
622
        self.set_interval(start, end)
623
        self.set_resource(issue["resource"])
624
625
class JugglerProject(JugglerCompoundKeyword):
626
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
627
    '''Template for TaskJuggler project'''
628
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
629
    LOG_STRING = "JugglerProject"
630
    DEFAULT_KEYWORD = 'project'
631
    DEFAULT_ID = "default" # id may be empty for some keywords
632
    DEFAULT_SUMMARY = 'Default Project' # no summary is possible everywhere
633
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
634
    def load_default_properties(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
635
        self.set_property(JugglerTimezone())
636
        self.set_property(JugglerOutputdir())
637
        self.set_interval()
638
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
639
    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...
640
        self.option2 = to_tj3interval(start, end)
641
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
642
643
class JugglerSource(JugglerCompoundKeyword):
644
    """
645
    The entire project skeleton
646
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
647
    Sets reports and folders for use with parser
648
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
649
    Must be extended with load_from_issue(self,issue) appending tasks 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
650
    """
651
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
652
    LOG_STRING = "JugglerSource"
653
    DEFAULT_KEYWORD = ''
654
    DEFAULT_ID = ''
655
    COMMENTS_HEADER = '''
656
    // TaskJuggler 3 source
657
    // generated by python-juggler (c) 2017 Andrew Gryaznov and others
658
    // https://github.com/grandrew/taskjuggler-python
659
    '''
660
    ENCLOSED_BLOCK = False
661
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
662
    def load_default_properties(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
663
        self.set_property(JugglerProject())
664
        self.set_property(JugglerResource())
665
        self.set_property(JugglerIcalreport())
666
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
667
    def _post_init(self, issue = None):
0 ignored issues
show
Coding Style introduced by
No space allowed around keyword argument assignment
Loading history...
668
        self.top = self
669
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
670
class GenericJuggler(object):
671
672
    '''Class for task-juggling generic results'''
673
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
674
    src = None
675
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
676
    def __init__(self):
677
        '''
678
        Construct a generic juggler object
679
680
        Args:
681
            none
682
        '''
683
684
        logging.info('generic load')
685
686
    def set_query(self, query):
687
        '''
688
        Set the query for the generic juggler object
689
690
        Args:
691
            query (str): The Query to run on generic server
692
        '''
693
694
        logging.info('Query: %s', query)
695
        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...
696
        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...
697
698
    @staticmethod
699
    def validate_tasks(tasks):
700
        '''
701
        Validate (and correct) tasks
702
703
        Args:
704
            tasks (list): List of JugglerTask's to validate
705
        '''
706
        for task in tasks:
707
            task.validate(tasks)
708
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
709
    def load_issues(self):
710
        raise NotImplementedError
711
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
712
    def add_task(self, task):
713
        '''
714
        Add task to current project
715
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
716
        Args:
717
            task (JugglerTask): a task to add
718
        '''
719
        if not self.src:
720
            self.juggle()
721
        self.src.set_property(task)
722
723
    def load_issues_incremetal(self):
724
        if self.loaded: return []
0 ignored issues
show
Coding Style introduced by
More than one statement on a single line
Loading history...
725
        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...
726
        return self.load_issues()
727
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
728
    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...
729
        return JugglerTask(issue)
730
731
    def load_issues_from_generic(self):
732
        '''
733
        Load issues from generic
734
735
        Returns:
736
            list: A list of dicts containing the generic tickets
737
        '''
738
        tasks = []
739
        busy = True
740
        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...
741
        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...
742
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
743
        while busy:
744
            try:
745
                issues = self.load_issues_incremetal()
746
            except NotImplementedError:
747
                logging.error('Loading Issues is not implemented in upstream library')
748
                return None
749
                
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
750
            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...
751
                logging.error('Could not get issue')
752
                return None
753
754
            if len(issues) <= 0:
755
                busy = False
756
757
            self.issue_count += len(issues)
758
759
            for issue in issues:
760
                logging.debug('Retrieved %s', repr(issue))
761
                tasks.append(self.create_task_instance(issue))
762
763
        self.validate_tasks(tasks)
764
765
        return tasks
766
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
767
    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...
768
        return JugglerSource()
769
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
770
    def juggle(self):
771
        """
772
        Export the loaded issues onto the JuggleSource structure
773
        """
774
        self.src = self.create_jugglersource_instance()
775
        issues = self.load_issues_from_generic()
776
        if not issues:
777
            # return None
778
            issues = []
779
        for issue in issues:
780
            self.src.set_property(issue)
781
        return self.src
782
783
    def write_file(self, output=None):
784
        '''
785
        Query generic and generate task-juggler output from given issues
786
787
        Args:
788
            output (str): Name of output file, for task-juggler
789
        '''
790
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
791
        if not self.src:
792
            self.juggle()
793
            
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
794
        s = str(self.src)
795
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
796
        if output and isinstance(output, str):
797
            with open(output, 'w') as out:
798
                out.write(s)
799
        elif output and isinstance(output, file):
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'file'
Loading history...
800
            output.write(s)
801
        # else:
802
        #     raise ValueError("output should be a filename string or a file handler")
803
        return s
804
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
805
    def read_ical_result(self, icalfile):
806
        tasks = self.src.walk(JugglerTask)
807
        cal = icalendar.Calendar.from_ical(file(icalfile).read())
0 ignored issues
show
Comprehensibility Best Practice introduced by
Undefined variable 'file'
Loading history...
808
        for ev in cal.walk('VEVENT'): # pylint:disable=no-member
809
            start_date = ev.decoded("DTSTART")
810
            end_date = ev.decoded("DTEND")
811
            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...
812
            for t in tasks:
813
                if t.id == id:
814
                    # logging.info("Testing %s" % str(t))
815
                    # logging.info("Got %s" % repr(t.walk(JugglerTaskAllocate)))
816
                    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
817
                    # ical does not support resource allocation reporting
818
                    # so we do not support multiple resource here
819
                    # and we don't support them yet anyways
820
                    t.set_property(JugglerBooking({
821
                        "resource":t.walk(JugglerTaskAllocate)[0].get_value(),
822
                        "start": start_date,
823
                        "end": end_date
824
                        }))
825
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
826
    def run(self, outfolder=None, infile=None):
827
        '''
828
        Run the taskjuggler task
829
830
        Args:
831
            output (str): Name of output file, for task-juggler
832
        '''
833
        if not self.src:
834
            self.juggle()
835
            
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
836
        if outfolder is None:
837
            outfolder = tempfile.mkdtemp("TJP")
838
        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...
839
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
840
        if infile is None:
841
            infile = tempfile.mkstemp(".tjp")[1]
842
        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...
843
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
844
        reportdir = self.src.walk(JugglerOutputdir)
845
        orig_rep = reportdir[0].get_value()
846
        reportdir[0].set_value(outfolder)
847
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
848
        ical_report_name = "calendar_out"
849
        ical_report_path = os.path.join(outfolder, ical_report_name)
850
        icalreport = self.src.walk(JugglerIcalreport)
851
        orig_cal = icalreport[0].get_value()
852
        icalreport[0].set_value(ical_report_name)
853
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
854
        self.write_file(infile)
855
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
856
        logging.debug("Running from %s to out %s" % (self.infile, self.outfolder))
0 ignored issues
show
Coding Style Best Practice introduced by
Specify string format arguments as logging function parameters
Loading history...
857
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
858
        subprocess.call(["/usr/bin/env", "tj3", infile])
859
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
860
        self.read_ical_result(ical_report_path+".ics")
861
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
862
        icalreport[0].set_value(orig_cal)
863
        reportdir[0].set_value(orig_rep)
864
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
865
        if DEBUG or logging.getLogger().getEffectiveLevel() >= logging.DEBUG: return
0 ignored issues
show
Coding Style introduced by
More than one statement on a single line
Loading history...
866
        shutil.rmtree(self.outfolder)
867
        os.remove(self.infile)
868
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
869
        # TODO HERE: load the ical file back to the actual tree (no tree support yet?)
870
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
871
    def clean(self):
872
        "clean after running"
873
        if DEBUG or logging.getLogger().getEffectiveLevel() >= logging.DEBUG: return
0 ignored issues
show
Coding Style introduced by
More than one statement on a single line
Loading history...
874
        try: shutil.rmtree(self.outfolder)
0 ignored issues
show
Coding Style introduced by
More than one statement on a single line
Loading history...
875
        except:  pass
0 ignored issues
show
Coding Style introduced by
Exactly one space required after :
Loading history...
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...
Coding Style introduced by
More than one statement on a single line
Loading history...
876
        try: os.remove(self.infile)
0 ignored issues
show
Coding Style introduced by
More than one statement on a single line
Loading history...
877
        except: pass
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...
Coding Style introduced by
More than one statement on a single line
Loading history...
878
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
879
    def walk(self, cls):
880
        if not self.src:
881
            self.juggle()
882
        return self.src.walk(cls)
883
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
884
    def __inter__(self):
885
        "provide dict(j) method to generate dictionary structure for tasks"
886
        raise NotImplementedError
887
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
888
    def __del__(self):
889
        self.clean()
890
891
0 ignored issues
show
coding-style introduced by
Trailing newlines
Loading history...
892