Passed
Push — master ( 8b9f7f...914492 )
by Marek
02:15
created

ssg.rule_yaml   A

Complexity

Total Complexity 21

Size/Duplication

Total Lines 178
Duplicated Lines 15.17 %

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
eloc 76
dl 27
loc 178
ccs 0
cts 72
cp 0
rs 10
c 0
b 0
f 0
wmc 21

8 Functions

Rating   Name   Duplication   Size   Complexity  
A add_key_value() 0 14 1
A get_yaml_contents() 0 17 2
A update_key_value() 27 27 4
A parse_prodtype() 0 6 2
A parse_from_yaml() 0 8 1
A remove_lines() 0 10 1
B find_section_lines() 0 48 7
A get_section_lines() 0 16 3

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
"""
2
The rule_yaml module provides various utility functions for handling YAML files
3
containing Jinja macros, without having to parse the macros.
4
"""
5
6
from __future__ import absolute_import
7
from __future__ import print_function
8
9
import os
10
import sys
11
from collections import namedtuple
12
import yaml
13
14
from .rules import get_rule_dir_yaml
15
from .utils import read_file_list
16
17
18
def find_section_lines(file_contents, sec):
19
    """
20
    Parses the given file_contents as YAML to find the section with the given identifier.
21
    Note that this does not call into the yaml library and thus correctly handles jinja
22
    macros at the expense of not being a strictly valid yaml parsing.
23
24
    Returns a list of namedtuples (start, end) of the lines where section exists.
25
    """
26
27
    # Hack to find a global key ("section"/sec) in a YAML-like file.
28
    # All indented lines until the next global key are included in the range.
29
    # For example:
30
    #
31
    # 0: not_it:
32
    # 1:     - value
33
    # 2: this_one:
34
    # 3:      - 2
35
    # 4:      - 5
36
    # 5:
37
    # 6: nor_this:
38
    #
39
    # for the section "this_one", the result [(2, 5)] will be returned.
40
    # Note that multiple sections may exist in a file and each will be
41
    # identified and returned.
42
    section = namedtuple('section', ['start', 'end'])
43
44
    sec_ranges = []
45
    sec_id = sec + ":"
46
    sec_len = len(sec_id)
47
    end_num = len(file_contents)
48
    line_num = 0
49
50
    while line_num < end_num:
51
        if len(file_contents[line_num]) >= sec_len:
52
            if file_contents[line_num][0:sec_len] == sec_id:
53
                begin = line_num
54
                line_num += 1
55
                while line_num < end_num:
56
                    nonempty_line = file_contents[line_num]
57
                    if nonempty_line and file_contents[line_num][0] != ' ':
58
                        break
59
                    line_num += 1
60
61
                end = line_num - 1
62
                sec_ranges.append(section(begin, end))
63
        line_num += 1
64
65
    return sec_ranges
66
67
68
def add_key_value(contents, key, start_line, new_value):
69
    """
70
    Adds a new key to contents with the given value after line start_line, returning
71
    the result. Also adds a blank line afterwards.
72
73
    Does not modify the value of contents.
74
    """
75
76
    new_contents = contents[:start_line]
77
    new_contents.append("%s: %s" % (key, new_value))
78
    new_contents.append("")
79
    new_contents.extend(contents[start_line:])
80
81
    return new_contents
82
83
84 View Code Duplication
def update_key_value(contents, key, old_value, new_value):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
85
    """
86
    Find key in the contents of a file and replace its value with the new value,
87
    returning the resulting file. This validates that the old value is constant and
88
    hasn't changed since parsing its value.
89
90
    Raises a ValueError when the key cannot be found in the given contents.
91
92
    Does not modify the value of contents.
93
    """
94
95
    new_contents = contents[:]
96
    old_line = key + ": " + old_value
97
    updated = False
98
99
    for line_num in range(0, len(new_contents)):
100
        line = new_contents[line_num]
101
        if line == old_line:
102
            new_contents[line_num] = key + ": " + new_value
103
            updated = True
104
            break
105
106
    if not updated:
107
        raise ValueError("For key:%s, cannot find the old value (%s) in the given "
108
                         "contents." % (key, old_value))
109
110
    return new_contents
111
112
113
def remove_lines(contents, lines):
114
    """
115
    Remove the lines of the section from the parsed file, returning the new contents.
116
117
    Does not modify the passed in contents.
118
    """
119
120
    new_contents = contents[:lines.start]
121
    new_contents.extend(contents[lines.end+1:])
122
    return new_contents
123
124
125
def parse_from_yaml(file_contents, lines):
126
    """
127
    Parse the given line range as a yaml, returning the parsed object.
128
    """
129
130
    new_file_arr = file_contents[lines.start:lines.end + 1]
131
    new_file = "\n".join(new_file_arr)
132
    return yaml.load(new_file)
133
134
135
def get_yaml_contents(rule_obj):
136
    """
137
    From a rule_obj description, return a namedtuple of (path, contents); where
138
    path is the path to the rule YAML and contents is the list of lines in
139
    the file.
140
    """
141
142
    file_description = namedtuple('file_description', ('path', 'contents'))
143
144
    yaml_file = get_rule_dir_yaml(rule_obj['dir'])
145
    if not os.path.exists(yaml_file):
146
        raise ValueError("Error: yaml file does not exist for rule_id:%s" %
147
                         rule_obj['id'], file=sys.stderr)
148
149
    yaml_contents = read_file_list(yaml_file)
150
151
    return file_description(yaml_file, yaml_contents)
152
153
154
def parse_prodtype(prodtype):
155
    """
156
    From a prodtype line, returns the set of products listed.
157
    """
158
159
    return set(map(lambda x: x.strip(), prodtype.split(',')))
160
161
162
def get_section_lines(file_path, file_contents, key_name):
163
    """
164
    From the given file_path and file_contents, find the lines describing the section
165
    key_name and returns the line range of the section.
166
    """
167
168
    section = find_section_lines(file_contents, key_name)
169
170
    if len(section) > 1:
171
        raise ValueError("Multiple instances (%d) of %s in %s; refusing to modify file." %
172
                         (len(section), key_name, file_path), file=sys.stderr)
173
174
    elif len(section) == 1:
175
        return section[0]
176
177
    return None
178