diff --git a/glances/globals.py b/glances/globals.py index 86fa9020..977c50f3 100644 --- a/glances/globals.py +++ b/glances/globals.py @@ -631,3 +631,84 @@ def exit_after(seconds, default=None): return wraps return decorator + + +def split_esc(input_string, sep=None, maxsplit=-1, esc='\\'): + """ + Return a list of the substrings in the input_string, using sep as the separator char + and esc as the escape character. + + sep + The separator used to split the input_string. + + When set to None (the default value), will split on any whitespace + character (including \n \r \t \f and spaces) unless the character is escaped + and will discard empty strings from the result. + maxsplit + Maximum number of splits. + -1 (the default value) means no limit. + esc + The character used to escape the separator. + + When set to None, this behaves equivalently to `str.split`. + Defaults to '\\\\' i.e. backslash. + + Splitting starts at the front of the input_string and works to the end. + + Note: escape characters in the substrings returned are removed. However, if + maxsplit is reached, escape characters in the remaining, unprocessed substring + are not removed, which allows split_esc to be called on it again. + """ + # Input validation + if not isinstance(input_string, str): + raise TypeError(f'must be str, not {input_string.__class__.__name__}') + str.split('', sep=sep, maxsplit=maxsplit) # Use str.split to validate sep and maxsplit + if esc is None: + return input_string.split( + sep=sep, maxsplit=maxsplit + ) # Short circuit to default implementation if the escape character is None + if not isinstance(esc, str): + raise TypeError(f'must be str or None, not {esc.__class__.__name__}') + if len(esc) == 0: + raise ValueError('empty escape character') + if len(esc) > 1: + raise ValueError('escape must be a single character') + + # Set up a simple state machine keeping track of whether we have seen an escape character + ret, esc_seen, i = [''], False, 0 + while i < len(input_string) and len(ret) - 1 != maxsplit: + if not esc_seen: + if input_string[i] == esc: + # Consume the escape character and transition state + esc_seen = True + i += 1 + elif sep is None and input_string[i].isspace(): + # Consume as much whitespace as possible + n = 1 + while i + n + 1 < len(input_string) and input_string[i + n : i + n + 1].isspace(): + n += 1 + ret.append('') + i += n + elif sep is not None and input_string[i : i + len(sep)] == sep: + # Consume the separator + ret.append('') + i += len(sep) + else: + # Otherwise just add the current char + ret[-1] += input_string[i] + i += 1 + else: + # Add the current char and transition state back + ret[-1] += input_string[i] + esc_seen = False + i += 1 + + # Append any remaining string if we broke early because of maxsplit + if i < len(input_string): + ret[-1] += input_string[i:] + + # If splitting on whitespace, discard empty strings from result + if sep is None: + ret = [sub for sub in ret if len(sub) > 0] + + return ret diff --git a/glances/plugins/plugin/model.py b/glances/plugins/plugin/model.py index f7c9c18b..8a8b3b00 100644 --- a/glances/plugins/plugin/model.py +++ b/glances/plugins/plugin/model.py @@ -27,6 +27,7 @@ from glances.globals import ( listkeys, mean, nativestr, + split_esc, ) from glances.history import GlancesHistory from glances.logger import logger @@ -933,7 +934,10 @@ class GlancesPluginModel: def read_alias(self): if self.plugin_name + '_' + 'alias' in self._limits: - return {i.split(':')[0].lower(): i.split(':')[1] for i in self._limits[self.plugin_name + '_' + 'alias']} + return { + split_esc(i, ':')[0].lower(): split_esc(i, ':')[1] + for i in self._limits[self.plugin_name + '_' + 'alias'] + } return {} def has_alias(self, header): diff --git a/tests/test_core.py b/tests/test_core.py index 8081be21..5929003d 100755 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -28,7 +28,18 @@ except ImportError: from glances import __version__ from glances.events_list import GlancesEventsList from glances.filter import GlancesFilter, GlancesFilterList -from glances.globals import BSD, LINUX, MACOS, SUNOS, WINDOWS, auto_unit, pretty_date, string_value_to_float, subsample +from glances.globals import ( + BSD, + LINUX, + MACOS, + SUNOS, + WINDOWS, + auto_unit, + pretty_date, + string_value_to_float, + subsample, + split_esc, +) from glances.main import GlancesMain from glances.outputs.glances_bars import Bar from glances.plugins.fs.zfs import zfs_enable, zfs_stats @@ -552,6 +563,30 @@ class TestGlances(unittest.TestCase): self.assertEqual(stats.get_plugin('cpu').get_alert(75, minimum=0, maximum=100, header='total'), 'WARNING_LOG') self.assertEqual(stats.get_plugin('cpu').get_alert(85, minimum=0, maximum=100, header='total'), 'CRITICAL_LOG') + def test_024_split_esc(self): + """Test split_esc function""" + print('INFO: [TEST_024] split_esc') + self.assertEqual(split_esc(r''), []) + self.assertEqual(split_esc('\\'), []) + self.assertEqual(split_esc(r'abcd'), [r'abcd']) + self.assertEqual(split_esc(r'abcd efg'), [r'abcd', r'efg']) + self.assertEqual(split_esc('abcd \n\t\f efg'), [r'abcd', r'efg']) + self.assertEqual(split_esc(r'abcd\ efg'), [r'abcd efg']) + self.assertEqual(split_esc(r'', ':'), ['']) + self.assertEqual(split_esc(r'abcd', ':'), [r'abcd']) + self.assertEqual(split_esc(r'abcd:efg', ':'), [r'abcd', r'efg']) + self.assertEqual(split_esc(r'abcd\:efg', ':'), [r'abcd:efg']) + self.assertEqual(split_esc(r'abcd:efg:hijk', ':'), [r'abcd', r'efg', r'hijk']) + self.assertEqual(split_esc(r'abcd\:efg:hijk', ':'), [r'abcd:efg', r'hijk']) + self.assertEqual(split_esc(r'abcd\:efg:hijk\:lmnop:qrs', ':', maxsplit=0), [r'abcd\:efg:hijk\:lmnop:qrs']) + self.assertEqual(split_esc(r'abcd\:efg:hijk\:lmnop:qrs', ':', maxsplit=1), [r'abcd:efg', r'hijk\:lmnop:qrs']) + self.assertEqual( + split_esc(r'abcd\:efg:hijk\:lmnop:qrs', ':', maxsplit=10), [r'abcd:efg', r'hijk:lmnop', r'qrs'] + ) + self.assertEqual(split_esc(r'ahellobhelloc', r'hello'), [r'a', r'b', r'c']) + self.assertEqual(split_esc(r'a\hellobhelloc', r'hello'), [r'ahellob', r'c']) + self.assertEqual(split_esc(r'ahe\llobhelloc', r'hello'), [r'ahellob', r'c']) + def test_093_auto_unit(self): """Test auto_unit classe""" print('INFO: [TEST_093] Auto unit')