Passed
Pull Request — develop (#458)
by
unknown
02:49
created

doorstop.core.item.Item.SIDEBAR()   A

Complexity

Conditions 1

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nop 2
dl 0
loc 5
rs 10
c 0
b 0
f 0
1
"""Representation of an item in a document."""
0 ignored issues
show
coding-style introduced by
Too many lines in module (1581/1000)
Loading history...
2
3
import os
4
import re
5
import functools
6
7
import pyficache
8
9
from doorstop import common
10
from doorstop.common import DoorstopError, DoorstopWarning, DoorstopInfo
11
from doorstop.core.base import (add_item, edit_item, delete_item,
12
                                auto_load, auto_save,
13
                                BaseValidatable, BaseFileObject)
14
from doorstop.core.types import Prefix, UID, Text, Level, Stamp, to_bool, REF #added REF
15
from doorstop.core import editor
16
from doorstop import settings
17
18
log = common.logger(__name__)
19
20
21
def requires_tree(func):
22
    """Decorator for methods that require a tree reference."""
23
    @functools.wraps(func)
24
    def wrapped(self, *args, **kwargs):
25
        """Wrapped method that requires a tree reference."""
26
        if not self.tree:
27
            name = func.__name__
28
            log.critical("`{}` can only be called with a tree".format(name))
29
            return None
30
        return func(self, *args, **kwargs)
31
    return wrapped
32
33
34
def requires_document(func):
35
    """Decorator for methods that require a document reference."""
36
    @functools.wraps(func)
37
    def wrapped(self, *args, **kwargs):
38
        """Wrapped method that requires a document reference."""
39
        if not self.document:
40
            name = func.__name__
41
            msg = "`{}` can only be called with a document".format(name)
42
            log.critical(msg)
43
            return None
44
        return func(self, *args, **kwargs)
45
    return wrapped
46
47
48
class Item(BaseValidatable, BaseFileObject):  # pylint: disable=R0902
49
50
    """Represents an item file with linkable text."""
51
52
    EXTENSIONS = '.yml', '.yaml'
53
54
    DEFAULT_LEVEL = Level('1.0')
55
    DEFAULT_ACTIVE = True
56
    DEFAULT_NORMATIVE = True
57
    DEFAULT_DERIVED = False
58
    DEFAULT_REVIEWED = Stamp()
59
    DEFAULT_TEXT = Text()
60
    DEFAULT_REF = ""
61
                                        # add 02.12.2019
62
    DEFAULT_Is_Req = True       
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
63
64
    DEFAULT_SPEC_RATIONALE = Text()
65
    DEFAULT_SPEC_STATUS = "In_Analysis"
66
    DEFAULT_SPEC_SHORT_DECRIPTION = ""
67
    DEFAULT_SPEC_ID = ""
68
    DEFAULT_SPEC_VERSION = 1
69
    DEFAULT_Assumption = ""
70
    DEFAULT_Ad_Info = ""
71
    DEFAULT_Author = ""
72
    DEFAULT_Generation = "Manual"
73
    DEFAULT_REFINE = ""
74
    DEFAULT_ASSOCIATED = ""
75
    DEFAULT_Validation_Ref = ""
76
    DEFAULT_Validation_Mean = "Review"
77
    DEFAULT_Verification_Ref = ""
78
    DEFAULT_Verification_Mean = "LAB_TEST"
79
    DEFAULT_Verification_Rationale = ""
80
    DEFAULT_Verification_Status = "Analysis"
81
    DEFAULT_VoV_Ref = ""
82
    DEFAULT_Alllocation = ""
83
84
                                        # end add 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
85
86
                                        # add 15.01.2020
87
    DEFAULT_SIDEBAR=''
0 ignored issues
show
Coding Style introduced by
Exactly one space required around assignment
Loading history...
88
    DEFAULT_EXTENSION=''
0 ignored issues
show
Coding Style introduced by
Exactly one space required around assignment
Loading history...
89
    DEFAULT_TITLE=''
0 ignored issues
show
Coding Style introduced by
Exactly one space required around assignment
Loading history...
90
                                        # end add 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
91
92
93
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
94
95
    def __init__(self, path, root=os.getcwd(), **kwargs):
96
        """Initialize an item from an existing file.
97
98
        :param path: path to Item file
99
        :param root: path to root of project
100
101
        """
102
        super().__init__()
103
        # Ensure the path is valid
104
        if not os.path.isfile(path):
105
            raise DoorstopError("item does not exist: {}".format(path))
106
        # Ensure the filename is valid
107
        filename = os.path.basename(path)
108
        name, ext = os.path.splitext(filename)
109
        try:
110
            UID(name).check()
111
        except DoorstopError:
112
            msg = "invalid item filename: {}".format(filename)
113
            raise DoorstopError(msg) from None
114
        # Ensure the file extension is valid
115
        if ext.lower() not in self.EXTENSIONS:
116
            msg = "'{0}' extension not in {1}".format(path, self.EXTENSIONS)
117
            raise DoorstopError(msg)
118
        # Initialize the item
119
        self.path = path
120
        self.root = root
121
        self.document = kwargs.get('document')
122
        self.tree = kwargs.get('tree')
123
        self.auto = kwargs.get('auto', Item.auto)
124
        # Set default values
125
        self._data['level'] = Item.DEFAULT_LEVEL
126
        self._data['active'] = Item.DEFAULT_ACTIVE
127
        self._data['normative'] = Item.DEFAULT_NORMATIVE
128
        self._data['derived'] = Item.DEFAULT_DERIVED
129
        self._data['reviewed'] = Item.DEFAULT_REVIEWED
130
        self._data['text'] = Item.DEFAULT_TEXT
131
        self._data['ref'] = set()
132
        self._data['links'] = set()
133
134
135
136
                                                # add 02.12.2019 / 06.12.2019
137
        self._data['Is_Req'] = Item.DEFAULT_Is_Req
138
139
        self._data['SPEC_RATIONALE'] = Item.DEFAULT_SPEC_RATIONALE
140
        self._data['SPEC_SHORT_DECRIPTION'] = Item.DEFAULT_SPEC_SHORT_DECRIPTION 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
141
        self._data['SPEC_ID'] = Item.DEFAULT_SPEC_ID
142
        self._data['SPEC_VERSION'] = Item.DEFAULT_SPEC_VERSION
143
        #self._data['SPEC_TEXT'] = Item.DEFAULT_SPEC_TEXT
144
        self._data['Assumption'] = Item.DEFAULT_Assumption
145
        self._data['Add_Info'] = Item.DEFAULT_Ad_Info
146
        self._data['Author'] = Item.DEFAULT_Author
147
        self._data['Generation'] = Item.DEFAULT_Generation
148
        self._data['REFINE'] = Item.DEFAULT_REFINE
149
        self._data['ASSOCIATED'] = Item.DEFAULT_ASSOCIATED
150
        self._data['SPEC_STATUS'] = Item.DEFAULT_SPEC_STATUS
151
        self._data['Validation_Ref'] = Item.DEFAULT_Validation_Ref
152
        self._data['Validation_Mean'] = Item.DEFAULT_Validation_Mean
153
        self._data['Verification_Ref'] = Item.DEFAULT_Verification_Ref
154
        self._data['Verification_Mean'] = Item.DEFAULT_Verification_Mean
155
        self._data['Verification_Rationale'] = Item.DEFAULT_Verification_Rationale
156
        self._data['Verification_Status'] = Item.DEFAULT_Verification_Status
157
        self._data['VoV_Ref'] = Item.DEFAULT_VoV_Ref
158
        self._data['Allocation'] = Item.DEFAULT_Alllocation
159
160
        # add 15.01.2020
161
        # Hinzufügen von attributen die nur auftreten wenn es sich bei dem yaml file nicht um ein requirmenet sondern
162
        # um eine Beschreibung überschrift oder sonstiges handelt... 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
163
        self._NO_REQ_data['EXTENSION']= Item.DEFAULT_EXTENSION
0 ignored issues
show
Coding Style introduced by
Exactly one space required before assignment
Loading history...
164
        self._NO_REQ_data['TITLE']=Item.DEFAULT_TITLE
0 ignored issues
show
Coding Style introduced by
Exactly one space required around assignment
Loading history...
165
        self._NO_REQ_data['SIDEBAR']= Item.DEFAULT_SIDEBAR
0 ignored issues
show
Coding Style introduced by
Exactly one space required before assignment
Loading history...
166
        # end add 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
167
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
168
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
169
                                                # end add 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
170
171
    def __repr__(self):
172
        return "Item('{}')".format(self.path)
173
174
    def __str__(self):
175
        if common.verbosity < common.STR_VERBOSITY:
176
            return str(self.uid)
177
        else:
178
            return "{} ({})".format(self.uid, self.relpath)
179
180
    def __lt__(self, other):
181
        if self.level == other.level:
182
            return self.uid < other.uid
183
        else:
184
            return self.level < other.level
185
186
    @staticmethod
187
    @add_item
188
    def new(tree, document, path, root, uid, level=None, auto=None):  # pylint: disable=R0913
189
        """Internal method to create a new item.
190
191
        :param tree: reference to the tree that contains this item
192
        :param document: reference to document that contains this item
193
194
        :param path: path to directory for the new item
195
        :param root: path to root of the project
196
        :param uid: UID for the new item
197
198
        :param level: level for the new item
199
        :param auto: automatically save the item
200
201
        :raises: :class:`~doorstop.common.DoorstopError` if the item
202
            already exists
203
204
        :return: new :class:`~doorstop.core.item.Item`
205
206
        """
207
        UID(uid).check()
208
        filename = str(uid) + Item.EXTENSIONS[0]
209
        path2 = os.path.join(path, filename)
210
        # Create the initial item file
211
        log.debug("creating item file at {}...".format(path2))
212
        Item._create(path2, name='item')
213
        # Initialize the item
214
        item = Item(path2, root=root, document=document, tree=tree, auto=False)    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
215
        item.level = level if level is not None else item.level
216
        if auto or (auto is None and Item.auto):
217
            item.save()
218
        # Return the item
219
        return item
220
221
    def load(self, reload=False):
222
        """Load the item's properties from its file."""
223
        if self._loaded and not reload:
224
            return
225
        log.debug("loading {}...".format(repr(self)))
226
        # Read text from file
227
        text = self._read(self.path)
228
        # Parse YAML data from text
229
        data = self._load(text, self.path)
230
231
        # Store parsed data
232
        # Herrausfinden on das YMAL file ein Requirement ist oder nicht...
233
        # Danach darus schlussfolgern welche attribute in dem file auftauche sollen und welche nicht 15.01.2020
234
        # Als erstes wird geschaut ob das data dictionary überhaupt daten entält ....
235
        if bool(data):
236
            if  data['Is_Req']:
237
238
                # tmp list to check for values that are only displayed when item is not a requirement ...
239
                tmp_list= ['EXTENSION','TITLE','SIDEBAR']
0 ignored issues
show
Coding Style introduced by
Exactly one space required before assignment
Loading history...
Coding Style introduced by
Exactly one space required after comma
Loading history...
240
241
242
                for key, value in data.items():
243
                   
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
244
                    #Is_Req add => vllt später anders um andere attribute zu sperren 09.01.2020
245
                    if key == 'Is_Req':
246
                        values = to_bool(value)
0 ignored issues
show
Unused Code introduced by
The variable values seems to be unused.
Loading history...
247
                    #end Is_Req add
248
                    if key == 'level':
249
                        value = Level(value)
250
                    elif key == 'active':
251
                        value = to_bool(value)
252
                    elif key == 'normative':
253
                        value = to_bool(value)
254
                    elif key == 'derived':
255
                        value = to_bool(value)
256
                    elif key == 'reviewed':
257
                        value = Stamp(value)    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
258
                    elif key == 'text':
259
                        #changed behhaviour of Text class in types.py to let linebreaks in ...
260
                        value = Text(value)
261
                    # add 02.12.2019
262
                    elif key == 'SPEC_RATIONALE':
263
                        value = Text(value)
264
                    elif key == 'SPEC_STATUS':
265
                        value = Text(value)
266
                                                # end add 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
267
268
                                                # add 09.12.2019
269
                    elif key == 'ref':            
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
270
                        value = set(REF(part) for part in value)
271
                        # print(value)
272
                                                # end add
273
                    elif key == 'links':
274
                        # print(type(value))
275
                        value = set(UID(part) for part in value)
276
                        # print(type(value))
277
                        # print(value)
278
                    elif key in tmp_list:
279
                            continue
0 ignored issues
show
Coding Style introduced by
The indentation here looks off. 24 spaces were expected, but 28 were found.
Loading history...
280
                        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
281
                    else:
282
                        if isinstance(value, str):
283
                            value = Text(value)
284
285
                    self._data[key] = value
286
            # add 15.01.2020
287
            else:
288
289
                for key, value in data.items():
290
                    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
291
                    if key == 'Is_Req':
292
                        values = to_bool(value)
293
                        self._data[key] = value
294
295
                    elif key == 'level':
296
                        value = Level(value)
297
                        self._data[key] = value
298
299
                    elif key == 'text':
300
                        value = Text(value)
301
                        self._data[key] = value
302
303
304
                    elif key == 'EXTENSION':
305
                        value=Text(value.upper())
0 ignored issues
show
Coding Style introduced by
Exactly one space required around assignment
Loading history...
306
                        self._NO_REQ_data[key] = value
307
                    elif key == 'TITLE':
308
                        value = Text(value)
309
                        self._NO_REQ_data[key] = value
310
                    elif key == 'SIDEBAR':
311
                        value =Text(value)
0 ignored issues
show
Coding Style introduced by
Exactly one space required after assignment
Loading history...
312
                        self._NO_REQ_data[key] = value
313
                    else:
314
                        # format not know
315
                        # will not be therer anymore after this procedure
316
                        continue                               
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
317
                        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
318
        # Set meta attributes
319
        self._loaded = True#
320
321
    @edit_item
322
    def save(self):
323
        """Format and save the item's properties to its file."""
324
        log.debug("saving {}...".format(repr(self)))
325
        # Format the data items
326
        data = self.data      
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
327
        # Dump the data to YAML    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
328
        text = self._dump(data)
329
        # Save the YAML to file
330
        self._write(text, self.path)
331
        # Set meta attributes
332
        self._loaded = False
333
        self.auto = True
334
335
    # properties #############################################################
336
337
   # add 09.12.2020 change behaviour for yaml dumping if file is not a requirement
338
    @property
339
    @auto_load
340
    def data(self):
341
        """Get all the item's data formatted for YAML dumping."""
342
        data = {}
343
       
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
344
        data['Is_Req'] = self._data['Is_Req']
345
   
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
346
        if data['Is_Req'] == True:
0 ignored issues
show
introduced by
Comparison to True should be just 'expr'
Loading history...
347
348
            for key, value in self._data.items():
349
            
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
350
                if key == 'level':
351
                    value = value.yaml
352
                elif key == 'text':       
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
353
                    value = value.yaml     
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
354
                elif key == 'ref':
355
                    # add 11.12.2019
356
                    value = [{str(i): i.stamp.yaml} for i in sorted(value)] #####here
357
                    #end add 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
358
                elif key == 'links':
359
                    value = [{str(i): i.stamp.yaml} for i in sorted(value)]
360
                elif key == 'reviewed':
361
                    value = value.yaml
362
                    # print(f'Value aus YAML: {value}')
363
                else:
364
                    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
365
                    if isinstance(value, str):
366
                        # length of "key_text: value_text"
367
                        length = len(key) + 2 + len(value)
368
                        if length > settings.MAX_LINE_LENGTH or '\n' in value:
369
                            value = Text.save_text(value)
370
                        else:
371
                            value = str(value)  # line is short enough as a strig
372
373
                # add 09.12.2020 change behaviour for yaml dumping if file is not a requirement
374
                if not key == 'Is_Req':
375
                    data[key] = value
376
         
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
377
            
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
378
        else:
379
           
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
380
            for key, value in self._data.items():
381
            
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
382
                if key == 'text':
383
                    value = value.yaml
384
                    data[key] = value
385
386
                elif key == 'level':
387
                    value = value.yaml
388
                    data[key] = value
389
390
                elif key == 'Is_Req':
391
                    value = to_bool(value)
392
                    data[key] = value             
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
393
394
            # Check Attribute die nicht vorhanden sind wenn item ein requirment ist
395
            for key, value in self._NO_REQ_data.items():
396
                if key == 'TITLE':
397
                    length = len(key) + 2 + len(value)
398
                    if length > settings.MAX_LINE_LENGTH or '\n' in value:
399
                        value = Text.save_text(value)
400
                    else:
401
                        value = str(value)  # line is short enough as a strig
402
403
                    data[key] = value
404
405
                elif key == 'EXTENSION':
406
                    length = len(key) + 2 + len(value)
407
                    if length > settings.MAX_LINE_LENGTH or '\n' in value:
408
                        value = Text.save_text(value)
409
                    else:
410
                        value = str(value)  # line is short enough as a strig
411
412
                    data[key] = value
413
414
                elif key == 'SIDEBAR':
415
                    length = len(key) + 2 + len(value)
416
                    if length > settings.MAX_LINE_LENGTH or '\n' in value:
417
                        value = Text.save_text(value)
418
                    else:
419
                        value = str(value)  # line is short enough as a strig
420
421
                    data[key] = value
422
                
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
423
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
424
        return data
425
426
        #end add 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
427
428
    @property
429
    def uid(self):
430
        """Get the item's UID."""
431
        filename = os.path.basename(self.path)
432
        return UID(os.path.splitext(filename)[0])
433
434
    @property
435
    def prefix(self):
436
        """Get the item UID's prefix."""
437
        return self.uid.prefix
438
439
    @property
440
    def number(self):
441
        """Get the item UID's number."""
442
        return self.uid.number
443
444
    @property
445
    @auto_load
446
    def level(self):
447
        """Get the item's level."""
448
        return self._data['level']
449
450
    @level.setter
451
    @auto_save
452
    @auto_load
453
    def level(self, value):
454
        """Set the item's level."""
455
        self._data['level'] = Level(value)
456
457
    @property
458
    def depth(self):
459
        """Get the item's heading order based on it's level."""
460
        return len(self.level)
461
462
    @property
463
    @auto_load
464
    def active(self):
465
        """Get the item's active status.
466
467
        An inactive item will not be validated. Inactive items are
468
        intended to be used for:
469
470
        - future requirements
471
        - temporarily disabled requirements or tests
472
        - externally implemented requirements
473
        - etc.
474
475
        """
476
        return self._data['active']
477
478
    @active.setter
479
    @auto_save
480
    @auto_load
481
    def active(self, value):
482
        """Set the item's active status."""
483
        self._data['active'] = to_bool(value)
484
485
    @property
486
    @auto_load
487
    def derived(self):
488
        """Get the item's derived status.
489
490
        A derived item does not have links to items in its parent
491
        document, but should still be linked to by items in its child
492
        documents.
493
494
        """
495
        return self._data['derived']
496
497
    @derived.setter
498
    @auto_save
499
    @auto_load
500
    def derived(self, value):
501
        """Set the item's derived status."""
502
        self._data['derived'] = to_bool(value)
503
504
    @property
505
    @auto_load
506
    def normative(self):
507
        """Get the item's normative status.
508
509
        A non-normative item should not have or be linked to.
510
        Non-normative items are intended to be used for:
511
512
        - headings
513
        - comments
514
        - etc.
515
516
        """
517
        return self._data['normative']
518
519
    @normative.setter
520
    @auto_save
521
    @auto_load
522
    def normative(self, value):
523
        """Set the item's normative status."""
524
        self._data['normative'] = to_bool(value)
525
526
    @property
527
    def heading(self):
528
        """Indicate if the item is a heading.
529
530
        Headings have a level that ends in zero and are non-normative.
531
532
        """
533
        return self.level.heading and not self.normative
534
535
    @heading.setter
536
    @auto_save
537
    @auto_load
538
    def heading(self, value):
539
        """Set the item's heading status."""
540
        heading = to_bool(value)
541
        if heading and not self.heading:
542
            self.level.heading = True
543
            self.normative = False
544
        elif not heading and self.heading:
545
            self.level.heading = False
546
            self.normative = True
547
548
    @property
549
    @auto_load
550
    def cleared(self):
551
        """Indicate if no links are suspect."""
552
        items = self.parent_items
553
        for uid in self.links:
554
            for item in items:
555
                if uid == item.uid:
556
                    if uid.stamp != item.stamp():
557
                        return False
558
        return True
559
560
561
# mabe setter and getter for reference here after implemeting clearRef function
562
        
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
563
564
    @cleared.setter
565
    @auto_save
566
    @auto_load
567
    def cleared(self, value):
568
        """Set the item's suspect link status."""
569
        self.clear(_inverse=not to_bool(value))
570
571
    @property
572
    @auto_load
573
    def reviewed(self):
574
        """Indicate if the item has been reviewed."""
575
        stamp = self.stamp(links=True)
576
 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
577
        if self._data['reviewed'] == Stamp(True):
578
            self._data['reviewed'] = stamp
579
        # if self.uid == "yrd-scorpos-002":
580
            # print(stamp)
581
        return self._data['reviewed'] == stamp
582
583
    @reviewed.setter
584
    @auto_save
585
    @auto_load
586
    def reviewed(self, value):
587
        """Set the item's review status."""
588
        self._data['reviewed'] = Stamp(value)
589
590
    @property
591
    @auto_load
592
    def text(self):
593
        """Get the item's text."""
594
        return self._data['text']
595
596
    @text.setter
597
    @auto_save
598
    @auto_load
599
    def text(self, value):
600
        """Set the item's text."""
601
        self._data['text'] = Text(value)
602
603
604
                                        # add 02.12.2019
605
606
    @property
607
    @auto_load
608
    def SPEC_RATIONALE(self):
609
        """Get the item's SPEC_RATIONALE."""
610
        return self._data['SPEC_RATIONALE']
611
612
    @SPEC_RATIONALE.setter
613
    @auto_save
614
    @auto_load
615
    def SPEC_RATIONALE(self, value):
616
        """Set the item's SPEC_RATIONALE."""
617
        self._data['SPEC_RATIONALE'] = Text(value)
618
619
620
621
622
623
    @property
624
    @auto_load
625
    def SPEC_SHORT_DECRIPTION(self):
626
        """Get the item's SPEC_SHORT_DECRIPTION."""
627
        return self._data['SPEC_SHORT_DECRIPTION']
628
629
    @SPEC_SHORT_DECRIPTION.setter
630
    @auto_save
631
    @auto_load
632
    def SPEC_SHORT_DECRIPTION(self, value):
633
        """Set the item's SPEC_SHORT_DECRIPTION."""
634
        self._data['SPEC_SHORT_DECRIPTION'] = Text(value)
635
636
637
638
639
640
    @property
641
    @auto_load
642
    def SPEC_ID(self):
643
        """Get the item's SPEC_ID."""
644
        return self._data['SPEC_ID']
645
646
    @SPEC_ID.setter
647
    @auto_save
648
    @auto_load
649
    def SPEC_ID(self, value):
650
        """Set the item's SPEC_ID."""
651
        self._data['SPEC_ID'] = Text(value)
652
653
654
655
656
657
    @property
658
    @auto_load
659
    def SPEC_VERSION(self):
660
        """Get the item's SPEC_VERSION."""
661
        return self._data['SPEC_VERSION']
662
663
    @SPEC_VERSION.setter
664
    @auto_save
665
    @auto_load
666
    def SPEC_VERSION(self, value):
667
        """Set the item's SPEC_VERSION."""
668
        self._data['SPEC_VERSION'] = value
669
670
671
672
673
674
    @property
675
    @auto_load
676
    def Assumption(self):
677
        """Get the item's Assumption."""
678
        return self._data['Assumption']
679
680
    @Assumption.setter
681
    @auto_save
682
    @auto_load
683
    def Assumption(self, value):
684
        """Set the item's Assumption."""
685
        self._data['Assumption'] = Text(value)
686
687
688
689
690
691
    @property
692
    @auto_load
693
    def Add_Info(self):
694
        """Get the item's Add_Info."""
695
        return self._data['Add_Info']
696
697
    @Add_Info.setter
698
    @auto_save
699
    @auto_load
700
    def Add_Info(self, value):
701
        """Set the item's Add_Info."""
702
        self._data['Add_Info'] = Text(value)
703
704
705
706
707
    @property
708
    @auto_load
709
    def Author(self):
710
        """Get the item's Author."""
711
        return self._data['Author']
712
713
    @Author.setter
714
    @auto_save
715
    @auto_load
716
    def Author(self, value):
717
        """Set the item's Author."""
718
        self._data['Author'] = Text(value)
719
720
721
722
723
    @property
724
    @auto_load
725
    def Generation(self):
726
        """Get the item's Generation."""
727
        return self._data['Generation']
728
729
    @Generation.setter
730
    @auto_save
731
    @auto_load
732
    def Generation(self, value):
733
        """Set the item's Generation."""
734
        self._data['Generation'] = Text(value)
735
736
737
738
739
740
    @property
741
    @auto_load
742
    def REFINE(self):
743
        """Get the item's REFINE."""
744
        return self._data['REFINE']
745
746
    @REFINE.setter
747
    @auto_save
748
    @auto_load
749
    def REFINE(self, value):
750
        """Set the item's REFINE."""
751
        self._data['REFINE'] = Text(value)
752
753
754
755
756
    @property
757
    @auto_load
758
    def ASSOCIATED(self):
759
        """Get the item's ASSOCIATED."""
760
        return self._data['ASSOCIATED']
761
762
    @ASSOCIATED.setter
763
    @auto_save
764
    @auto_load
765
    def ASSOCIATED(self, value):
766
        """Set the item's ASSOCIATED."""
767
        self._data['ASSOCIATED'] = Text(value)
768
769
770
771
772
    @property
773
    @auto_load
774
    def SPEC_STATUS(self):
775
        """Get the item's SPEC_STATUS."""
776
        return self._data['SPEC_STATUS']
777
778
    @SPEC_STATUS.setter
779
    @auto_save
780
    @auto_load
781
    def SPEC_STATUS(self, value):
782
        """Set the item's SPEC_STATUS."""
783
        self._data['SPEC_STATUS'] = Text(value)
784
785
786
787
788
789
    @property
790
    @auto_load
791
    def Validation_Ref(self):
792
        """Get the item's Validation_Ref."""
793
        return self._data['Validation_Ref']
794
795
    @Validation_Ref.setter
796
    @auto_save
797
    @auto_load
798
    def Validation_Ref(self, value):
799
        """Set the item's Validation_Ref."""
800
        self._data['Validation_Ref'] = Text(value)
801
802
803
804
805
    @property
806
    @auto_load
807
    def Validation_Mean(self):
808
        """Get the item's Validation_Mean."""
809
        return self._data['Validation_Mean']
810
811
    @Validation_Mean.setter
812
    @auto_save
813
    @auto_load
814
    def Validation_Mean(self, value):
815
        """Set the item's Validation_Mean."""
816
        self._data['Validation_Mean'] = Text(value)
817
818
819
820
821
    @property
822
    @auto_load
823
    def Verification_Ref(self):
824
        """Get the item's Verification_Ref."""
825
        return self._data['Verification_Ref']
826
827
    @Verification_Ref.setter
828
    @auto_save
829
    @auto_load
830
    def Verification_Ref(self, value):
831
        """Set the item's Verification_Ref."""
832
        self._data['Verification_Ref'] = Text(value)
833
834
835
836
837
    @property
838
    @auto_load
839
    def Verification_Mean(self):
840
        """Get the item's Verification_Mean."""
841
        return self._data['Verification_Mean']
842
843
    @Verification_Mean.setter
844
    @auto_save
845
    @auto_load
846
    def Verification_Mean(self, value):
847
        """Set the item's Verification_Mean."""
848
        self._data['Verification_Mean'] = Text(value)
849
850
851
852
853
854
    @property
855
    @auto_load
856
    def Verification_Rationale(self):
857
        """Get the item's Verification_Rationale."""
858
        return self._data['Verification_Rationale']
859
860
    @Verification_Rationale.setter
861
    @auto_save
862
    @auto_load
863
    def Verification_Rationale(self, value):
864
        """Set the item's Verification_Rationale."""
865
        self._data['Verification_Rationale'] = Text(value)
866
867
868
869
870
871
    @property
872
    @auto_load
873
    def Verification_Status(self):
874
        """Get the item's Verification_Status."""
875
        return self._data['Verification_Status']
876
877
    @Verification_Status.setter
878
    @auto_save
879
    @auto_load
880
    def Verification_Status(self, value):
881
        """Set the item's Verification_Status."""
882
        self._data['Verification_Status'] = Text(value)
883
884
885
886
887
888
    @property
889
    @auto_load
890
    def VoV_Ref(self):
891
        """Get the item's VoV_Ref."""
892
        return self._data['VoV_Ref']
893
894
    @VoV_Ref.setter
895
    @auto_save
896
    @auto_load
897
    def VoV_Ref(self, value):
898
        """Set the item's VoV_Ref."""
899
        self._data['VoV_Ref'] = Text(value)
900
901
902
903
904
905
    @property
906
    @auto_load
907
    def Allocation(self):
908
        """Get the item's Allocation."""
909
        return self._data['Allocation']
910
911
    @Allocation.setter
912
    @auto_save
913
    @auto_load
914
    def Allocation(self, value):
915
        """Set the item's Allocation."""
916
        self._data['Allocation'] = Text(value)
917
918
919
920
921
    @property
922
    @auto_load
923
    def SPEC_STATUS(self):
0 ignored issues
show
Bug introduced by
method already defined line 774
Loading history...
924
        """Get the item's SPEC_STATUS."""
925
        return self._data['SPEC_STATUS']
926
927
    @SPEC_STATUS.setter
928
    @auto_save
929
    @auto_load
930
    def SPEC_STATUS(self, value):
931
        """Set the item's SPEC_STATUS."""
932
        self._data['SPEC_STATUS'] = Text(value)
933
934
935
            	                            # end add
936
937
    # Is_Req add 09.01.2020
938
939
    @property
940
    @auto_load
941
    def Is_Req(self):
942
        """Get the items Is_Req boolean."""
943
        return self._data['Is_Req']
944
945
    @Is_Req.setter
946
    @auto_save
947
    @auto_load
948
    def Is_Req(self, value):
949
        """Set the item's Is_Req boolean."""
950
        self._data['Is_Req'] = to_bool(value)
951
952
    # end Is_Req add
953
954
    # add 16.01.2020
955
    # Es werden die attribute hinzugefügt die nur erscheinen wenn es sich nicht um ein Requirment handelt
956
957
    @property
958
    @auto_load
959
    def EXTENSION(self):
960
        """Get the items EXTENSION string."""
961
        return self._NO_REQ_data['EXTENSION']
962
963
    @EXTENSION.setter
964
    @auto_save
965
    @auto_load
966
    def EXTENSION(self, value):
967
        """Set the item's EXTENSION value."""
968
        self._NO_REQ_data['EXTENSION'] = Text(value)
969
970
971
972
    @property
973
    @auto_load
974
    def TITLE(self):
975
        """Get the items TITLE string."""
976
        return self._NO_REQ_data['TITLE']
977
978
    @TITLE.setter
979
    @auto_save
980
    @auto_load
981
    def TITLE(self, value):
982
        """Set the item's TITLE value."""
983
        self._NO_REQ_data['TITLE'] = Text(value)
984
985
986
987
    @property
988
    @auto_load
989
    def SIDEBAR(self):
990
        """Get the items SIDEBAR string."""
991
        return self._NO_REQ_data['SIDEBAR']
992
993
    @SIDEBAR.setter
994
    @auto_save
995
    @auto_load
996
    def SIDEBAR(self, value):
997
        """Set the item's SIDEBAR value."""
998
        self._NO_REQ_data['SIDEBAR'] = Text(value)
999
   
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1000
1001
# add 10.12.2019
1002
    @property
1003
    @auto_load
1004
    def ref(self):
1005
        """Get the item's external file reference.
1006
1007
        An external reference can be part of a line in a text file or
1008
        the filename of any type of file.
1009
1010
        """
1011
        return sorted(self._data['ref'])
1012
1013
    @ref.setter
1014
    @auto_save
1015
    @auto_load
1016
    def ref(self, value):
1017
        """Set the item's external file reference."""
1018
        self._data['ref'] = set(REF(v) for v in value)
1019
1020
# end add 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1021
1022
    @property
1023
    @auto_load
1024
    def links(self):
1025
        """Get a list of the item UIDs this item links to."""
1026
        return sorted(self._data['links'])
1027
1028
    @links.setter
1029
    @auto_save
1030
    @auto_load
1031
    def links(self, value):
1032
        """Set the list of item UIDs this item links to."""
1033
        self._data['links'] = set(UID(v) for v in value)
1034
1035
    @property
1036
    def parent_links(self):
1037
        """Get a list of the item UIDs this item links to."""
1038
        return self.links  # alias
1039
1040
    @parent_links.setter
1041
    def parent_links(self, value):
1042
        """Set the list of item UIDs this item links to."""
1043
        self.links = value  # alias
1044
1045
    @property
1046
    @requires_tree
1047
    def parent_items(self):
1048
        """Get a list of items that this item links to."""
1049
        items = []
1050
        for uid in self.links:
1051
            try:
1052
                item = self.tree.find_item(uid)
1053
            except DoorstopError:
1054
                item = UnknownItem(uid)
1055
                log.warning(item.exception)
1056
            items.append(item)
1057
        return items
1058
1059
    @property
1060
    @requires_tree
1061
    @requires_document
1062
    def parent_documents(self):
1063
        """Get a list of documents that this item's document should link to.
1064
1065
        .. note::
1066
1067
           A document only has one parent.
1068
1069
        """
1070
        try:
1071
            return [self.tree.find_document(self.document.prefix)]
1072
        except DoorstopError:
1073
            log.warning(Prefix.UNKNOWN_MESSGE.format(self.document.prefix))
1074
            return []
1075
1076
    # actions ################################################################
1077
1078
    @auto_save
1079
    def edit(self, tool=None):
1080
        """Open the item for editing.
1081
1082
        :param tool: path of alternate editor
1083
1084
        """
1085
        # Lock the item
1086
        if self.tree:
1087
            self.tree.vcs.lock(self.path)
1088
        # Open in an editor
1089
        editor.edit(self.path, tool=tool)
1090
        # Force reloaded
1091
        self._loaded = False
1092
1093
    @auto_save
1094
    @auto_load
1095
    def link(self, value):
1096
        """Add a new link to another item UID.
1097
1098
        :param value: item or UID
1099
1100
        """
1101
        uid = UID(value)
1102
        log.info("linking to '{}'...".format(uid))
1103
        self._data['links'].add(uid)
1104
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1105
    # add 13.12.2019
1106
    @auto_save
1107
    @auto_load
1108
    def add_ref(self,value):
0 ignored issues
show
Coding Style introduced by
Exactly one space required after comma
Loading history...
1109
        """Add new reference to Item
1110
        :param value: item or UID
1111
 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1112
        """
1113
        ref = REF(value)
1114
        log.info("referencing to'{}'...".format(ref))
1115
        self._data['ref'].add(ref)
1116
1117
    # end add 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1118
1119
1120
1121
    @auto_save
1122
    @auto_load
1123
    def unlink(self, value):
1124
        """Remove an existing link by item UID.
1125
1126
        :param value: item or UID
1127
1128
        """
1129
        uid = UID(value)
1130
        try:
1131
            self._data['links'].remove(uid)
1132
        except KeyError:
1133
            log.warning("link to {0} does not exist".format(uid))
1134
1135
    def get_issues(self, **kwargs):
0 ignored issues
show
Bug introduced by
Parameters differ from overridden 'get_issues' method
Loading history...
1136
        """Yield all the item's issues.
1137
1138
        :return: generator of :class:`~doorstop.common.DoorstopError`,
1139
                              :class:`~doorstop.common.DoorstopWarning`,
1140
                              :class:`~doorstop.common.DoorstopInfo`
1141
1142
        """
1143
1144
1145
        assert kwargs.get('document_hook') is None
1146
        assert kwargs.get('item_hook') is None
1147
        log.info("checking item {}...".format(self))
1148
        # Verify the file can be parsed
1149
        self.load()
1150
       
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1151
        # add 09.01.2020 -> If item is not a requirement do nothing
1152
        if self._data['Is_Req'] == False:
0 ignored issues
show
introduced by
Comparison to False should be 'not expr'
Loading history...
1153
            self.save()
1154
            if not self.text:
1155
                yield DoorstopWarning("no text")
1156
            return
1157
        # Skip inactive items
1158
        if not self.active:
1159
            log.info("skipped inactive item: {}".format(self))
1160
            return
1161
        # Delay item save if reformatting
1162
        if settings.REFORMAT:
1163
            self.auto = False
1164
        # Check text
1165
        if not self.text:
1166
            yield DoorstopWarning("no text")
1167
        # Check external references
1168
        if settings.CHECK_REF:
1169
            try:
1170
                self.find_ref()
1171
            except DoorstopError as exc:
1172
                yield exc
1173
        # Check links
1174
        if not self.normative and self.links:
1175
            yield DoorstopWarning("non-normative, but has links")
1176
        # Check links against the document
1177
        if self.document:
1178
            yield from self._get_issues_document(self.document)
1179
        # Check links against the tree
1180
        if self.tree:
1181
            yield from self._get_issues_tree(self.tree)
1182
        # Check links against both document and tree
1183
        if self.document and self.tree:
1184
            yield from self._get_issues_both(self.document, self.tree)
1185
        # Check review status
1186
        # print('REVIEWD:')
1187
        # print(self.reviewed)
1188
        if not self.reviewed:
1189
            if settings.CHECK_REVIEW_STATUS:
1190
                # print('HALLO')
1191
                yield DoorstopWarning("unreviewed changes")
1192
        # Reformat the file
1193
        if settings.REFORMAT:
1194
            log.debug("reformatting item {}...".format(self))
1195
            self.save()
1196
1197
                                                                             # add 13.12.2019
1198
1199
        # check ob references inaktiv gesetzt werden müssen
1200
        for ref in self.ref:
1201
            # falls stamp noch keinen wert hat bekommt er hier den aktuellen
1202
            if ref.stamp != self.stamp(ID=ref.value):
1203
                if settings.CHECK_REF_STATUS:
1204
                    yield DoorstopWarning('suspect reference {}'.format(ref))
1205
1206
        # print(self)
1207
        # print(self.ref)
1208
            
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1209
1210
                                                                            # end add
1211
1212
    def _get_issues_document(self, document):
1213
        """Yield all the item's issues against its document."""
1214
        log.debug("getting issues against document...")
1215
        # Verify an item's UID matches its document's prefix
1216
        if self.prefix != document.prefix:
1217
            msg = "prefix differs from document ({})".format(document.prefix)
1218
            yield DoorstopInfo(msg)
1219
        # Verify an item has upward links
1220
        if all((document.parent,
1221
                self.normative,
1222
                not self.derived)) and not self.links:
1223
            msg = "no links to parent document: {}".format(document.parent)
1224
            yield DoorstopWarning(msg)
1225
        # Verify an item's links are to the correct parent
1226
        for uid in self.links:
1227
            try:
1228
                prefix = uid.prefix
1229
            except DoorstopError:
1230
                msg = "invalid UID in links: {}".format(uid)
1231
                yield DoorstopError(msg)
1232
            else:
1233
                if document.parent and prefix != document.parent:
1234
                    # this is only 'info' because a document is allowed
1235
                    # to contain items with a different prefix, but
1236
                    # Doorstop will not create items like this
1237
                    msg = "parent is '{}', but linked to: {}".format(
1238
                        document.parent, uid)
1239
                    yield DoorstopInfo(msg)
1240
1241 View Code Duplication
    def _get_issues_tree(self, tree):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1242
        """Yield all the item's issues against its tree."""
1243
        log.debug("getting issues against tree...")
1244
        # Verify an item's links are valid
1245
    
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1246
        identifiers = set()
1247
        for uid in self.links:
1248
            try:
1249
                item = tree.find_item(uid)
1250
            except DoorstopError:
1251
                identifiers.add(uid)  # keep the invalid UID
1252
                msg = "linked to unknown item: {}".format(uid)
1253
                yield DoorstopError(msg)
1254
            else:
1255
                # check the linked item
1256
                if not item.active:
1257
                    msg = "linked to inactive item: {}".format(item)
1258
                    yield DoorstopInfo(msg)
1259
                if not item.normative:
1260
                    msg = "linked to non-normative item: {}".format(item)
1261
                    yield DoorstopWarning(msg)
1262
                # check the link status
1263
                
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1264
                # wenn bis es bis jetzt noch keinen stamp gibt ... funktioniert allerdings glaube ich nicht ....
1265
                if uid.stamp == Stamp(True):
1266
                    uid.stamp = item.stamp()  #... convert True to a stamp
1267
                
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1268
1269
                elif uid.stamp != item.stamp():
1270
                    # 12.12.2019
1271
                    # print('STAMPS FOR LINKS')
1272
                    # print(item.stamp())
1273
                    # print(uid.stamp)
1274
                    # print(uid)
1275
                    if settings.CHECK_SUSPECT_LINKS:
1276
                        msg = "suspect link: {}".format(item)
1277
                        yield DoorstopWarning(msg)
1278
                # reformat the item's UID
1279
                identifier2 = UID(item.uid, stamp=uid.stamp)
1280
                identifiers.add(identifier2)
1281
        # Apply the reformatted item UIDs
1282
        if settings.REFORMAT:
1283
            self._data['links'] = identifiers
1284
1285
    def _get_issues_both(self, document, tree):
1286
        """Yield all the item's issues against its document and tree."""
1287
        log.debug("getting issues against document and tree...")
1288
        # Verify an item is being linked to (child links)
1289
        if settings.CHECK_CHILD_LINKS and self.normative:
1290
            items, documents = self._find_child_objects(document=document,
1291
                                                        tree=tree,
1292
                                                        find_all=False)
1293
            if not items:
1294
                for document in documents:
0 ignored issues
show
unused-code introduced by
Redefining argument with the local name 'document'
Loading history...
1295
                    msg = "no links from child document: {}".format(document)
1296
                    yield DoorstopWarning(msg)
1297
1298
1299
1300
# add 11.12.2019 umbau in einen for loop 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1301
1302
    @requires_tree
1303
    def find_ref(self, paths=False):
1304
        """Get the external file reference and line number.
1305
1306
        :raises: :class:`~doorstop.common.DoorstopError` when no
1307
            reference is found
1308
1309
        :return: relative path to file or None (when no reference
1310
            set),
1311
            line number (when found in file) or None (when found as
1312
            filename) or None (when no reference set)
1313
1314
        """
1315
                                                        # add 06.01.2020
1316
        # Choose generator to iterate over:
1317
        if paths:
1318
            iterator = paths
1319
        else:
1320
            iterator = self.tree.vcs.paths
1321
                                                         # end add 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1322
        # Return immediately if no external reference
1323
        if not self.ref:
1324
            log.debug("no external reference to search for")
1325
            return None, None
1326
        # Update the cache
1327
        if not settings.CACHE_PATHS:
1328
            pyficache.clear_file_cache()
1329
        # Search for the external reference
1330
        # initialiseren zweier listen die mit den referencen gefüllt werden
1331
        ref_found = {}
1332
1333
        ### hier umbau in loop // self.ref wird überall mit ref.value ersetzt um zugriff auf die einzelnen Strings zu haben !
1334
        for ref in self.ref:
1335
           
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1336
            # hinzufügen eines dictionaries mit true == gefunden und false == nicht gefunden -> tracking der gefundenen referencen im code 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1337
            ref_found[ref.value] = False
1338
            
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1339
1340
            log.debug("seraching for ref '{}'...".format(ref.value))
1341
            pattern = r"(\b|\W){}(\b|\W)".format(re.escape(ref.value))
1342
            log.trace("regex: {}".format(pattern))
1343
            regex = re.compile(pattern)
1344
            for path, filename, relpath in iterator:
1345
                # Skip the item's file while searching              
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1346
                if path == self.path:
1347
                    # pfad zur yaml datei in der referencen stehen ...
1348
                    continue
1349
                # Check for a matching filename
1350
                if filename == ref.value:
1351
                    return relpath, 'Directory'
1352
                # Skip extensions that should not be considered text
1353
                if os.path.splitext(filename)[-1] in settings.SKIP_EXTS:
1354
                    continue
1355
                # Search for the reference in the file
1356
                                                                    # add 11.12.2019 // fill lists added before // 16.12.2019 --> dictionary mit false als value 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1357
                                                                    # wenn nicht gefunden ansonsten tuple mit file/line 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1358
                lines = pyficache.getlines(path)
1359
                if lines is None:
1360
                    log.trace("unable to read lines from: {}".format(path))
1361
                    continue
1362
                
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1363
                for lineno, line in enumerate(lines, start=1):
1364
                    if regex.search(line):
1365
                        log.debug("found ref: {}".format(relpath))
1366
                        if ref_found[ref.value] == False:
0 ignored issues
show
introduced by
Comparison to False should be 'not expr'
Loading history...
1367
                            ref_found[ref.value] = (relpath, lineno)
1368
                        else:
1369
                            msg = f"found aaaa multiple times same refernce in code!\n=>('{relpath}', {lineno}) and {ref_found[ref.value]}"
1370
                            raise DoorstopError(msg)
1371
1372
        if ref_found:
1373
            return ref_found
1374
        else: 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1375
           return None
0 ignored issues
show
Coding Style introduced by
The indentation here looks off. 12 spaces were expected, but 11 were found.
Loading history...
1376
                                                                    # end add 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1377
1378
1379
# end add
1380
1381
1382
1383
1384
1385
1386
    def find_child_links(self, find_all=True):
1387
        """Get a list of item UIDs that link to this item (reverse links).
1388
1389
        :param find_all: find all items (not just the first) before returning
1390
1391
        :return: list of found item UIDs
1392
1393
        """
1394
        items, _ = self._find_child_objects(find_all=find_all)
1395
        identifiers = [item.uid for item in items]
1396
        return identifiers
1397
1398
    child_links = property(find_child_links)
1399
1400
    def find_child_items(self, find_all=True):
1401
        """Get a list of items that link to this item.
1402
1403
        :param find_all: find all items (not just the first) before returning
1404
1405
        :return: list of found items
1406
1407
        """
1408
        items, _ = self._find_child_objects(find_all=find_all)
1409
        return items
1410
1411
    child_items = property(find_child_items)
1412
1413
    def find_child_documents(self):
1414
        """Get a list of documents that should link to this item's document.
1415
1416
        :return: list of found documents
1417
1418
        """
1419
        _, documents = self._find_child_objects(find_all=False)
1420
        return documents
1421
1422
    child_documents = property(find_child_documents)
1423
1424
    def _find_child_objects(self, document=None, tree=None, find_all=True):
1425
        """Get lists of child items and child documents.
1426
1427
        :param document: document containing the current item
1428
        :param tree: tree containing the current item
1429
        :param find_all: find all items (not just the first) before returning
1430
1431
        :return: list of found items, list of all child documents
1432
1433
        """
1434
        child_items = []
1435
        child_documents = []
1436
        document = document or self.document
1437
        tree = tree or self.tree
1438
        if not document or not tree:
1439
            return child_items, child_documents
1440
        # Find child objects
1441
        log.debug("finding item {}'s child objects...".format(self))
1442
        for document2 in tree:
1443
            if document2.parent == document.prefix:
1444
                child_documents.append(document2)
1445
                # Search for child items unless we only need to find one
1446
                if not child_items or find_all:
1447
                    for item2 in document2:
1448
                        if self.uid in item2.links:
1449
                            child_items.append(item2)
1450
                            if not find_all:
1451
                                break
1452
        # Display found links
1453
        if child_items:
1454
            if find_all:
1455
                joined = ', '.join(str(i) for i in child_items)
1456
                msg = "child items: {}".format(joined)
1457
            else:
1458
                msg = "first child item: {}".format(child_items[0])
1459
            log.debug(msg)
1460
            joined = ', '.join(str(d) for d in child_documents)
1461
            log.debug("child documents: {}".format(joined))
1462
        return sorted(child_items), child_documents
1463
1464
    @auto_load
1465
    def stamp(self, links=False, ID=False):
1466
        """Hash the item's key content for later comparison."""
1467
        # add 15.12.2019 values werden nach ID Parameter ausgewählt
1468
        if not ID:
1469
            values = [self.uid, self.text, self.Assumption] #self.ref] reference nicht mit reinnhemen weil sie siich verändert bei clear ref 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1470
                                            # un damit das säubern reihenfolgenahbhängig wird
1471
1472
1473
            if links:
1474
                values.extend(self.links)
1475
             
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1476
            
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1477
1478
        else:
1479
1480
        # end add 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1481
            values = [self.text,self.Assumption, ID]
0 ignored issues
show
Coding Style introduced by
Exactly one space required after comma
Loading history...
1482
            # print(f'VALUES:{values}')
1483
            
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1484
        return Stamp(*values)
1485
1486
    @auto_save
1487
    @auto_load
1488
    def clear(self, _inverse=False):
1489
        """Clear suspect links."""
1490
        log.info("clearing suspect links...")
1491
        items = self.parent_items
1492
        for uid in self.links:
1493
            for item in items:
1494
                if uid == item.uid:
1495
                    if _inverse:
1496
                        uid.stamp = Stamp()
1497
                    else:
1498
                        uid.stamp = item.stamp()
1499
1500
1501
    @auto_save
1502
    @auto_load
1503
    def review(self):
1504
        """Mark the item as reviewed."""
1505
        log.info("marking item as reviewed...")
1506
        self._data['reviewed'] = self.stamp(links=True)
1507
       
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1508
1509
1510
        # add 15.12.2019
1511
    @auto_save
1512
    @auto_load
1513
    def clearRef(self, referenceID):
1514
        """change suspect refernece status"""
1515
        log.info("marking reference as reviewed...")
1516
        # Mit found_id wird sichergestellt, dass auch eine ID gefunden wurd
1517
        # ansonsten wir fehler ausgegen das die angegebne ID nicht gefunden werdne kann.. 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1518
        found_id = False 
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1519
        if referenceID == 'all':
1520
            found_id = True
1521
            for ref in self.ref:
1522
                # item aktuellen stamp aus spezifischer id und text geben
1523
                ref.stamp = self.stamp(ID=ref.value)
1524
1525
        else:
1526
             for ref in self.ref:
0 ignored issues
show
Coding Style introduced by
The indentation here looks off. 12 spaces were expected, but 13 were found.
Loading history...
1527
                 if referenceID == ref.value:
0 ignored issues
show
Coding Style introduced by
The indentation here looks off. 16 spaces were expected, but 17 were found.
Loading history...
1528
                    found_id = True
1529
                    ref.stamp = self.stamp(ID=ref.value)
1530
1531
        if not found_id:
1532
            raise DoorstopError("ID does not exist: {}".format(referenceID))
1533
1534
           
0 ignored issues
show
Coding Style introduced by
Trailing whitespace
Loading history...
1535
    # end add
1536
1537
    @delete_item
1538
    def delete(self, path=None):
1539
        """Delete the item."""
1540
        pass  # the item is deleted in the decorated method
0 ignored issues
show
Unused Code introduced by
Unnecessary pass statement
Loading history...
1541
1542
1543
1544
class UnknownItem(object):
0 ignored issues
show
introduced by
Class 'UnknownItem' inherits from object, can be safely removed from bases in python3
Loading history...
1545
1546
    """Represents an unknown item, which doesn't have a path."""
1547
1548
    UNKNOWN_PATH = '???'  # string to represent an unknown path
1549
1550
    normative = False  # do not include unknown items in traceability
1551
1552
    def __init__(self, value, spec=Item):
1553
        self._uid = UID(value)
1554
        self._spec = dir(spec)  # list of attribute names for warnings
1555
        msg = UID.UNKNOWN_MESSAGE.format(k='', u=self.uid)
1556
        self.exception = DoorstopError(msg)
1557
1558
    def __str__(self):
1559
        return Item.__str__(self)
1560
1561
    def __getattr__(self, name):
1562
        if name in self._spec:
1563
            log.debug(self.exception)
1564
        return self.__getattribute__(name)
1565
1566
    @property
1567
    def uid(self):
1568
        """Get the item's UID."""
1569
        return self._uid
1570
1571
    prefix = Item.prefix
1572
    number = Item.number
1573
1574
    @property
1575
    def relpath(self):
1576
        """Get the unknown item's relative path string."""
1577
        return "@{}???".format(os.sep, self.UNKNOWN_PATH)
0 ignored issues
show
Bug introduced by
Too many arguments for format string
Loading history...
1578
1579
    def stamp(self):  # pylint: disable=R0201
1580
        """Return an empty stamp."""
1581
        return Stamp(None)
1582