Files
vmware-tools/vCert-6.1.1-20260401/lib/text_utils.py
T
2026-06-23 11:27:54 +08:00

275 lines
9.0 KiB
Python

# Copyright (c) 2024 Broadcom. All Rights Reserved.
# The term "Broadcom" refers to Broadcom Inc.
# and/or its subsidiaries.
import re
from lib.environment import Environment
class TextFilter(object):
"""
A utility class for filtering multi-line text. The filtering methods in this
class will return self to allow method chaining
Example of usage:
result = TextFilter(text).contain(filter).head(1).remove(unwanted_chars).get_text()
"""
def __init__(self, text):
self.lines = text.splitlines()
self.before_lines = 0
self.after_lines = 0
self.invert_match = False
def get_text(self):
"""
Return the result as a string, joined by '\n'
"""
return "\n".join(self.lines)
def get_lines(self):
"""
Return the current filtering result as a string list
"""
return self.lines
def set_options(self, before_lines=0, after_lines=0, invert_match=False):
"""
Set the matching option
:param before_lines: the result will also contain the before_lines lines
before the matched line
:param after_lines: the result will also contain the after_lines lines
after the matched lines
:param invert_match: If True, the match operations will return lines that
that don't match to the search pattern
"""
self.before_lines = before_lines
self.after_lines = after_lines
self.invert_match = invert_match
return self
def reset_options(self):
"""
Reset the matching option
"""
self.before_lines = self.after_lines = 0
self.invert_match = False
return self
def mod_match_result(self, match_result):
return not match_result if self.invert_match else match_result
def get_lines_with_extended_indices(self, indices):
"""
Extend the matching lines indices by applying before_lines and after_lines
matching options
:param indices: indices to extend
:return: extended indices
"""
if self.before_lines > 0 or self.after_lines > 0:
new_indices = []
for index in indices:
if self.before_lines > 0:
begin_index = index - self.before_lines
if begin_index < 0:
begin_index = 0
new_indices.extend([idx for idx in range(begin_index, index)])
if self.after_lines > 0:
end_index = index + self.after_lines + 1
if end_index > len(self.lines):
end_index = len(self.lines)
new_indices.extend([idx for idx in range(index + 1, end_index)])
indices.extend(new_indices)
indices = sorted(set(indices))
return [line for idx, line in enumerate(self.lines) if idx in indices]
def match(self, pattern):
"""
Filter lines using regular expression match
:param pattern: regular expression pattern for matching the lines
"""
indices = [idx for idx, line in enumerate(self.lines)
if self.mod_match_result(re.match(pattern, line))]
self.lines = self.get_lines_with_extended_indices(indices)
return self
def contain(self, keyword):
"""
Filter the lines using simple keywords
:param keyword: keyword for matching the lines
"""
indices = [idx for idx, line in enumerate(self.lines)
if self.mod_match_result(keyword in line)]
self.lines = self.get_lines_with_extended_indices(indices)
return self
def start_with(self, text):
"""
Filter the lines that started with {text}
:param text: text for filtering
"""
indices = [idx for idx, line in enumerate(self.lines)
if self.mod_match_result(line.find(text) == 0)]
self.lines = self.get_lines_with_extended_indices(indices)
return self
def head(self, count=1):
"""
Include only the first {count} lines to be included in the result
:param count: total of lines to be included in the result. If the value is
a negative value between -1 and -[number of lines], it will include all
lines except of the last {count} lines at the end
"""
if count < 0:
count += len(self.lines)
if count < 0:
count = 0
self.lines = [line for i, line in enumerate(self.lines) if i < count]
return self
def tail(self, count=1):
"""
Includes only the last {count} lines
:param count: total of lines to be included. If the value is a negative
value between -1 and -[#lines], it will include all lines except the
first {count} lines at the beginning
:return:
"""
length = len(self.lines)
if count < 0:
count += length
self.lines = [line for i, line in enumerate(self.lines) if i >= length - count]
return self
def match_block(self, pattern_begin, pattern_end, concatenate=False):
"""
Filter the lines using begin and end pattern
:param pattern_begin: the pattern for starting the matching block
:param pattern_end: the pattern for stopping the matching block
:param concatenate: if True, the matching block will be concatenated
as a single string joined using `\n' character
"""
lines = []
block = []
matching = False
for line in self.lines:
if not matching and re.match(pattern_begin, line):
matching = True
block = []
if matching:
block.append(line)
if matching and re.match(pattern_end, line):
matching = False
if concatenate:
lines.append('\n'.join(block))
else:
lines.extend(block)
self.lines = lines
return self
def apply(self, method):
"""
Apply method on all lines. The lines will be replaced using the values returned
by the method
:param method: method to be applied
"""
self.lines = [method(line) for line in self.lines]
return self
def replace(self, keyword1, keyword2):
"""
Update the lines by replacing keywords
:param keyword1: keyword to be replaced
:param keyword2: keyword to be used for replacing
"""
self.lines = [line.replace(keyword1, keyword2) for line in self.lines]
return self
def remove(self, keyword):
"""
Update all lines by removing {keyword}
:param keyword: text to be removed
"""
return self.replace(keyword, '')
def remove_white_spaces(self):
"""
Remove white spaces in all lines
"""
return self.apply(remove_white_spaces_method)
def cut(self, delimiter=' ', fields=None, new_delimiter=' ', include_empty=False):
"""
Split the line using delimiter and include only the required fields.
This method tries to mimics cut(1) command
:param delimiter: The delimiter to be used for split the line
:param fields: list of indices to be included (0 based index)
:param new_delimiter: If specified, the result will be concenated back
using the new_delimiter
:param include_empty: If True, the result may also contain empty lines
when the request fields are not available (default: False)
"""
lines = []
if fields is None:
fields = []
for line in self.lines:
result = []
words = line.split(delimiter)
for idx in fields:
if idx == -1:
idx = len(words) - 1
if idx < len(words):
result.append(words[idx])
if result or include_empty:
lines.append(new_delimiter.join(result))
self.lines = lines
return self
def get_count(self):
"""
Get the count of lines in the current result
"""
return len(self.lines)
def dump(self):
"""
Dump the current contents. This can be use for debugging purpose
"""
print('Contents:', '\n'.join(self.lines), sep='\n')
return self
def remove_white_spaces_method(text):
"""
Method for removing white spaces to be specified to TextUtil.apply
"""
return re.sub(r'\s+', '', text)
def translate_text(*text, sep=''):
"""
Reformat text using mapping in the environment variables
:param text: String parameters
:param sep: Separator if the text contains multiple string parameters
:return: reformatted text
"""
text = sep.join(text)
if text and '{' in text:
env_map = Environment.get_environment().get_map()
text = text.format_map(env_map)
return text