1#
2# Copyright (C) 2016 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17import itertools
18import os
19
20DEFAULT_COMMENT_CHAR = '#'
21
22
23def ItemsToStr(input_list):
24    '''Convert item in a list to string.
25
26    Args:
27        input_list: list of objects, the list to convert
28
29    Return:
30        A list of string where objects were converted to string using str function.
31        None if input list is None.
32    '''
33    if not input_list:
34        return input_list
35    return list(map(str, input_list))
36
37
38def ExpandItemDelimiters(input_list,
39                         delimiter,
40                         strip=False,
41                         to_str=False,
42                         remove_empty=True):
43    '''Expand list items that contain the given delimiter.
44
45    Args:
46        input_list: list of string, a list whose item may contain a delimiter
47        delimiter: string
48        strip: bool, whether to strip items after expanding. Default is False
49        to_str: bool, whether to convert output items in string.
50                Default is False
51        remove_empty: bool, whether to remove empty string in result list.
52                      Will not remove None items. Default: True
53
54    Returns:
55        The expended list, which may be the same with input list
56        if no delimiter found; None if input list is None
57    '''
58    if input_list is None:
59        return None
60
61    do_strip = lambda s: s.strip() if strip else s
62    do_str = lambda s: str(s) if to_str else s
63
64    expended_list_generator = (item.split(delimiter) for item in input_list)
65    result = [
66        do_strip(do_str(s))
67        for s in itertools.chain.from_iterable(expended_list_generator)
68    ]
69    return filter(lambda s: str(s) != '', result) if remove_empty else result
70
71
72def DeduplicateKeepOrder(input):
73    '''Remove duplicate items from a sequence while keeping the item order.
74
75    Args:
76        input: a sequence that might have duplicated items.
77
78    Returns:
79        A deduplicated list where item order is kept.
80    '''
81    return MergeUniqueKeepOrder(input)
82
83
84def MergeUniqueKeepOrder(*lists):
85    '''Merge two list, remove duplicate items, and order.
86
87    Args:
88        lists: any number of lists
89
90    Returns:
91        A merged list where items are unique and original order is kept.
92    '''
93    seen = set()
94    return [
95        x for x in itertools.chain(*lists) if not (x in seen or seen.add(x))
96    ]
97
98
99def LoadListFromCommentedTextFile(file_path,
100                                  to_str=True,
101                                  to_strip=True,
102                                  exclude_empty_line=True,
103                                  exclude_comment_line=True,
104                                  exclude_trailing_comment=True,
105                                  remove_duplicates=False,
106                                  remove_line_breaks=True,
107                                  comment_char=DEFAULT_COMMENT_CHAR):
108    '''Read commented text file into a list of lines.
109
110        Comments or empty lines will be excluded by default.
111
112        Args:
113            file_path: string, path to file
114            to_str: bool, whether to convert lines to string in result list.
115                    Default value is True.
116            to_strip: bool, whether to strip lines in result list.
117                      Default value is True.
118            exclude_empty_line: bool, whether to exclude empty items in result list
119                                Default value is True.
120            exclude_comment_line: bool, whether to exclude lines that only contains comments.
121                                  If a line starts with spaces and ends with comments it
122                                  will still be excluded even if to_trim is False.
123                                  Default value is True.
124            exclude_trailing_comment: bool, whether to remove trailing comments
125                                      from result items.
126                                      Default value is True.
127            remove_duplicates: bool, whether to remove duplicate items in output list.
128                               Default value is False.
129            remove_line_breaks: bool, whether to remove trailing trailing
130                                new line characters from result items.
131                                Default value is True.
132            comment_char: string, character to denote comment.
133                          Default value is pound (#).
134
135        Returns:
136            a list of string. None if file does not exist.
137        '''
138    if not os.path.isfile(file_path):
139        logging.error('The path provided is not a file or does not exist: %s',
140                      file_path)
141        return None
142
143    with open(file_path, 'r') as f:
144        return LoadListFromCommentedText(
145            f.read(),
146            to_str,
147            to_strip,
148            exclude_empty_line,
149            exclude_comment_line,
150            exclude_trailing_comment,
151            remove_duplicates,
152            remove_line_breaks,
153            comment_char=DEFAULT_COMMENT_CHAR)
154
155
156def LoadListFromCommentedText(text,
157                              to_str=True,
158                              to_strip=True,
159                              exclude_empty_line=True,
160                              exclude_comment_line=True,
161                              exclude_trailing_comment=True,
162                              remove_duplicates=False,
163                              remove_line_breaks=True,
164                              comment_char=DEFAULT_COMMENT_CHAR):
165    '''Read commented text into a list of lines.
166
167        Comments or empty lines will be excluded by default.
168
169        Args:
170            text: string, text to parse
171            to_str: bool, whether to convert lines to string in result list.
172                    Default value is True.
173            to_strip: bool, whether to strip lines in result list.
174                      Default value is True.
175            exclude_empty_line: bool, whether to exclude empty items in result list
176                                Default value is True.
177            exclude_comment_line: bool, whether to exclude lines that only contains comments.
178                                  If a line starts with spaces and ends with comments it
179                                  will still be excluded even if to_trim is False.
180                                  Default value is True.
181            exclude_trailing_comment: bool, whether to remove trailing comments
182                                      from result items.
183                                      Default value is True.
184            remove_duplicates: bool, whether to remove duplicate items in output list.
185                               Default value is False.
186            remove_line_breaks: bool, whether to remove trailing trailing
187                                new line characters from result items.
188                                Default value is True.
189            comment_char: string, character to denote comment.
190                          Default value is pound (#).
191
192        Returns:
193            a list of string.
194        '''
195    lines = text.splitlines(not remove_line_breaks)
196
197    if to_str:
198        lines = map(str, lines)
199
200    if exclude_trailing_comment:
201
202        def RemoveComment(line):
203            idx = line.find(comment_char)
204            if idx < 0:
205                return line
206            else:
207                return line[:idx]
208
209        lines = map(RemoveComment, lines)
210
211    if to_strip:
212        lines = map(lambda line: line.strip(), lines)
213
214    if exclude_comment_line:
215        lines = filter(lambda line: not line.strip().startswith(comment_char),
216                       lines)
217
218    if exclude_empty_line:
219        lines = filter(bool, lines)
220
221    if remove_duplicates:
222        lines = DeduplicateKeepOrder(lines)
223
224    return lines
225