예제 #1
0
 def test_remove_colors(self):
     """Test our ANSI/ECMA REGEXP colors removal method"""
     self.assertFalse(Colors.remove_colors(str(Colors.CLEAR)))
     self.assertEqual(Colors.remove_colors('\x1b[0;31mTEST\x1b[0;0m'),
                      'TEST')
     self.assertEqual(Colors.remove_colors('\x1b[0nTEST\xde\xad\xbe\xaf'),
                      '\x1b[0nTEST\xde\xad\xbe\xaf')
예제 #2
0
 def test_get_level_color(self):
     """Test for `Colors.get_level_color`"""
     # [25] (GREEN) < 50 < 75
     self.assertEqual(Colors.get_level_color(25, 50, 75), Colors.GREEN_NORMAL)
     # 33 < [34] (YELLOW) < 66
     self.assertEqual(Colors.get_level_color(34, 33, 66), Colors.YELLOW_NORMAL)
     # 33 < 66 < [90] (RED)
     self.assertEqual(Colors.get_level_color(90, 33, 66), Colors.RED_NORMAL)
예제 #3
0
    def _get_colors_palette(self):
        """Build and return a 8-color palette, with Unicode characters if allowed"""
        # On systems with non-Unicode locales, we imitate '\u2588' character
        # ... with '#' to display the terminal colors palette.
        use_unicode = self._configuration.get('colors_palette')['use_unicode']

        return ' '.join([
            '{normal}{character}{bright}{character}{clear}'.format(
                normal=Colors((0, i)),
                bright=Colors((1, i)),
                character=('\u2588' if use_unicode else '#'),
                clear=Colors.CLEAR) for i in range(37, 30, -1)
        ])
예제 #4
0
    def _get_colors_palette(self) -> str:
        """Build and return a 8-color palette, with Unicode characters if allowed"""
        # On systems with non-Unicode locales, we imitate '\u2588' character
        # ... with '#' to display the terminal colors palette.
        # Archey >= v4.8.0, Unicode is enabled by default.
        use_unicode = self.options.get('use_unicode', True)

        return ' '.join([
            '{normal}{character}{bright}{character}{clear}'.format(
                normal=Colors((0, i)),
                bright=Colors((1, i)),
                character=('\u2588' if use_unicode else '#'),
                clear=Colors.CLEAR) for i in range(37, 30, -1)
        ])
예제 #5
0
    def __init__(self, **kwargs):
        # Fetches passed arguments.
        self._format_to_json = kwargs.get('format_to_json')

        try:
            # If set, force the distribution to `preferred_distribution` argument.
            self._distribution = Distributions(
                kwargs.get('preferred_distribution'))
        except ValueError:
            # If not (or unknown), run distribution detection.
            self._distribution = Distributions.run_detection()

        # Fetch the colors palette related to this distribution.
        self._colors_palette = COLORS_DICT[self._distribution]

        # If `os-release`'s `ANSI_COLOR` option is set, honor it.
        ansi_color = Distributions.get_ansi_color()
        if ansi_color and Configuration().get(
                'colors_palette')['honor_ansi_color']:
            # Replace each Archey integrated colors by `ANSI_COLOR`.
            self._colors_palette = len(self._colors_palette) * \
                [Colors.escape_code_from_attrs(ansi_color)]

        # Each entry will be added to this list
        self._entries = []
        # Each class output will be added in the list below afterwards
        self._results = []
예제 #6
0
    def __init__(self, **kwargs):
        # Fetches passed arguments.
        self._format_to_json = kwargs.get('format_to_json')  # type: int

        try:
            # If set, force the distribution to `preferred_distribution` argument.
            self._distribution = Distributions(kwargs.get('preferred_distribution'))
        except ValueError:
            # If not (or unknown), run distribution detection.
            self._distribution = Distributions.run_detection()

        # Retrieve distribution's logo module before copying and DRY-ing its attributes.
        logo_module = lazy_load_logo_module(self._distribution.value)
        self._logo, self._colors = logo_module.LOGO.copy(), logo_module.COLORS.copy()

        # If `os-release`'s `ANSI_COLOR` option is set, honor it.
        ansi_color = Distributions.get_ansi_color()
        if ansi_color and Configuration().get('honor_ansi_color'):
            # Replace each Archey integrated colors by `ANSI_COLOR`.
            self._colors = len(self._colors) * [Colors.escape_code_from_attrs(ansi_color)]

        # Each entry will be added to this list
        self._entries = []
        # Each class output will be added in the list below afterwards
        self._results = []
예제 #7
0
    def __init__(self):
        # The configuration object is needed to retrieve some settings below.
        configuration = Configuration()

        # This dictionary will store values obtained from sub-processes calls.
        self._usage = {'used': 0.0, 'total': 0.0}

        self._run_df_usage()
        self._run_btrfs_usage()

        # Check whether at least one media could be found.
        if not self._usage['total']:
            self.value = configuration.get('default_strings')['not_detected']
            return

        # Fetch the user-defined disk limits from configuration.
        disk_limits = configuration.get('limits')['disk']

        # Based on the disk percentage usage, select the corresponding level color.
        level_color = Colors.get_level_color(
            (self._usage['used'] / (self._usage['total'] or 1)) * 100,
            disk_limits['warning'], disk_limits['danger'])

        self.value = '{0}{1} GiB{2} / {3} GiB'.format(
            level_color, round(self._usage['used'], 1), Colors.CLEAR,
            round(self._usage['total'], 1))
예제 #8
0
파일: ram.py 프로젝트: ingrinder/archey4
    def output(self, output):
        """
        Adds the entry to `output` after pretty-formatting the RAM usage with color and units.
        """
        if not self.value:
            # Fall back on the default behavior if no RAM usage could be detected.
            super().output(output)
            return

        # DRY some constants
        used = self.value['used']
        total = self.value['total']
        unit = self.value['unit']
        # Fetch the user-defined RAM limits from configuration.
        ram_limits = self._configuration.get('limits')['ram']

        # Based on the RAM percentage usage, select the corresponding level color.
        level_color = Colors.get_level_color(
            (used / total) * 100, ram_limits['warning'], ram_limits['danger'])

        output.append(
            self.name, '{0}{1} {unit}{2} / {3} {unit}'.format(level_color,
                                                              int(used),
                                                              Colors.CLEAR,
                                                              int(total),
                                                              unit=unit))
예제 #9
0
    def output(self, output):
        """
        Adds the entry to `output` after pretty-formatting the RAM usage with color and units.
        """
        if not self.value:
            # Fall back on the default behavior if no RAM usage could be detected.
            super().output(output)
            return

        # DRY some constants
        used = self.value['used']
        total = self.value['total']
        unit = self.value['unit']

        # Based on the RAM percentage usage, select the corresponding level color.
        level_color = Colors.get_level_color(
            (used / total) * 100,
            self.options.get('warning_use_percent', 33.3),
            self.options.get('danger_use_percent', 66.7)
        )

        output.append(
            self.name,
            f'{level_color}{int(used)} {unit}{Colors.CLEAR} / {int(total)} {unit}'
        )
예제 #10
0
    def output(self, output):
        """Adds the entry to `output` after pretty-formatting with colors palette"""
        text_output = (self.value or self._default_strings.get('not_detected'))
        if Colors.should_color_output():
            text_output += ' ' + self._get_colors_palette()

        output.append(self.name, text_output)
예제 #11
0
 def test_distribution_logos_no_empty_lines(self):
     """Check that distribution logos do not contain (useless) empty lines"""
     for distribution in Distributions:
         for i, line in enumerate(LOGOS_DICT[distribution]):
             self.assertTrue(
                 Colors.remove_colors(line).strip(),
                 msg='[{0}] line index {1}, got a forbidden empty line'.
                 format(distribution, i))
예제 #12
0
    def __init__(self, **kwargs):
        # Fetches passed arguments.
        self._format_to_json = kwargs.get('format_to_json')
        preferred_logo_style = (kwargs.get('preferred_logo_style')
                                or '').upper()

        try:
            # If set, force the distribution to `preferred_distribution` argument.
            self._distribution = Distributions(
                kwargs.get('preferred_distribution'))
        except ValueError:
            # If not (or unknown), run distribution detection.
            self._distribution = Distributions.get_local()

        # Retrieve distribution's logo module before copying and DRY-ing its attributes.
        logo_module = lazy_load_logo_module(self._distribution.value)

        # If set and available, fetch an alternative logo style from module.
        if preferred_logo_style and hasattr(logo_module,
                                            f"LOGO_{preferred_logo_style}"):
            self._logo = getattr(logo_module,
                                 f"LOGO_{preferred_logo_style}").copy()
            self._colors = getattr(logo_module,
                                   f"COLORS_{preferred_logo_style}").copy()
        else:
            self._logo, self._colors = logo_module.LOGO.copy(
            ), logo_module.COLORS.copy()

        configuration = Configuration()

        # If `os-release`'s `ANSI_COLOR` option is set, honor it.
        ansi_color = Distributions.get_ansi_color()
        if ansi_color and configuration.get("honor_ansi_color"):
            # Replace each Archey integrated colors by `ANSI_COLOR`.
            self._colors = len(
                self._colors) * [Colors.escape_code_from_attrs(ansi_color)]

        entries_color = configuration.get("entries_color")
        self._entries_color = (Colors.escape_code_from_attrs(entries_color)
                               if entries_color else self._colors[0])

        # Each entry will be added to this list
        self._entries = []
        # Each class output will be added in the list below afterwards
        self._results = []
예제 #13
0
    def __init__(self):
        # The configuration object is needed to retrieve some settings below.
        configuration = Configuration()

        terminal = os.getenv(
            'TERM',
            configuration.get('default_strings')['not_detected'])

        # On systems with non-Unicode locales, we imitate '\u2588' character
        # ... with '#' to display the terminal colors palette.
        # This is the default option for backward compatibility.
        use_unicode = configuration.get('colors_palette')['use_unicode']
        colors = ' '.join([
            '{normal}{character}{bright}{character}{clear}'.format(
                normal=Colors((0, i)),
                bright=Colors((1, i)),
                character=('\u2588' if use_unicode else '#'),
                clear=Colors.CLEAR) for i in range(37, 30, -1)
        ])

        self.value = '{0} {1}'.format(terminal, colors)
예제 #14
0
 def test_ansi_ecma_regexp(self):
     """Test our ANSI/ECMA REGEXP compiled pattern"""
     self.assertTrue(ANSI_ECMA_REGEXP.match(str(Colors.CLEAR)))
     self.assertTrue(ANSI_ECMA_REGEXP.match(str(Colors.RED_NORMAL)))
     self.assertTrue(
         ANSI_ECMA_REGEXP.match(Colors.escape_code_from_attrs('0;31;45')))
     self.assertFalse(ANSI_ECMA_REGEXP.match(''))
     self.assertFalse(ANSI_ECMA_REGEXP.match('\x1b[m'))
     self.assertFalse(ANSI_ECMA_REGEXP.match('\x1b[0M'))
     # Check that matched groups contain the whole code (no capturing groups).
     self.assertEqual(
         len(''.join(
             ANSI_ECMA_REGEXP.findall(
                 str(Colors.GREEN_NORMAL) + 'NOT_A_COLOR' +
                 str(Colors.CLEAR)))),
         len(str(Colors.GREEN_NORMAL) + str(Colors.CLEAR)))
예제 #15
0
파일: ram.py 프로젝트: techroy23/archey4
    def __init__(self):
        # Fetch the user-defined RAM limits from configuration.
        ram_limits = Configuration().get('limits')['ram']

        try:
            ram = ''.join(
                filter(
                    re.compile('Mem').search,
                    check_output(
                        ['free', '-m'],
                        env={'LANG': 'C'}, universal_newlines=True
                    ).splitlines()
                )
            ).split()
            used = float(ram[2])
            total = float(ram[1])

        except (IndexError, FileNotFoundError):
            # An in-digest one-liner to retrieve memory info into a dictionary
            with open('/proc/meminfo') as file:
                ram = {
                    i.split(':')[0]: float(i.split(':')[1].strip(' kB')) / 1024
                    for i in filter(None, file.read().splitlines())
                }

            total = ram['MemTotal']
            # Here, let's imitate Neofetch's behavior.
            # See <https://github.com/dylanaraps/neofetch/wiki/Frequently-Asked-Questions>.
            used = total + ram['Shmem'] - (
                ram['MemFree'] + ram['Cached'] + ram['SReclaimable'] + ram['Buffers'])
            # Imitates what `free` does when the obtained value happens to be incorrect.
            # See <https://gitlab.com/procps-ng/procps/blob/master/proc/sysinfo.c#L790>.
            if used < 0:
                used = total - ram['MemFree']

        # Based on the RAM percentage usage, select the corresponding level color.
        level_color = Colors.get_level_color(
            (used / total) * 100,
            ram_limits['warning'], ram_limits['danger']
        )

        self.value = '{0}{1} MiB{2} / {3} MiB'.format(
            level_color,
            int(used),
            Colors.CLEAR,
            int(total)
        )
예제 #16
0
    def output(self, output) -> None:
        if not self.value:
            # Fall back on the default behavior if load average values could not be detected.
            super().output(output)
            return

        # DRY constant thresholds.
        warning_threshold = self.options.get("warning_threshold", 1.0)
        danger_threshold = self.options.get("danger_threshold", 2.0)

        output.append(
            self.name,
            " ".join([
                str(
                    Colors.get_level_color(load_avg, warning_threshold,
                                           danger_threshold)) + str(load_avg) +
                str(Colors.CLEAR) for load_avg in self.value
            ]),
        )
예제 #17
0
    def test_should_color_output(self):
        """Test for `Colors.should_color_output`"""
        # Clear cache filled by `functools.lru_cache` decorator.
        Colors.should_color_output.cache_clear()

        with patch('archey.colors.Environment.CLICOLOR_FORCE', True):
            self.assertTrue(Colors.should_color_output())

        Colors.should_color_output.cache_clear()

        with patch('archey.colors.Environment.CLICOLOR_FORCE', False), \
                patch('archey.colors.Environment.NO_COLOR', True):
            self.assertFalse(Colors.should_color_output())

        Colors.should_color_output.cache_clear()

        with patch('archey.colors.Environment.CLICOLOR_FORCE', False), \
                patch('archey.colors.Environment.NO_COLOR', False):
            with patch('archey.colors.sys.stdout.isatty', return_value=False):
                with patch('archey.colors.Environment.CLICOLOR', True):
                    self.assertFalse(Colors.should_color_output())

                Colors.should_color_output.cache_clear()

                with patch('archey.colors.Environment.CLICOLOR', False):
                    self.assertFalse(Colors.should_color_output())

                Colors.should_color_output.cache_clear()

            with patch('archey.colors.sys.stdout.isatty', return_value=True):
                # Default case : STDOUT is a TTY and `CLICOLOR` is (by default) set.
                with patch('archey.colors.Environment.CLICOLOR', True):
                    self.assertTrue(Colors.should_color_output())

                Colors.should_color_output.cache_clear()

                with patch('archey.colors.Environment.CLICOLOR', False):
                    self.assertFalse(Colors.should_color_output())

                Colors.should_color_output.cache_clear()
예제 #18
0
 def test_escape_code_from_attrs(self):
     """Test for `Colors.escape_code_from_attrs`"""
     self.assertEqual(Colors.escape_code_from_attrs('0;31'), '\x1b[0;31m')
     self.assertEqual(Colors.escape_code_from_attrs('0;31;45'),
                      '\x1b[0;31;45m')
예제 #19
0
    def test_distribution_logos_consistency(self):
        """
        Verify each distribution identifier got a logo module.
        Verify each distribution logo module contain `LOGO` & `COLORS` ("truthy") attributes.
        Also check they got _consistent_ widths across their respective lines.
        Additionally verify they don't contain any (useless) empty line.

        This test also indirectly checks `lazy_load_logo_module` behavior!
        """
        distributions_identifiers = Distributions.get_distribution_identifiers(
        )

        for i, logo_module_info in enumerate(pkgutil.iter_modules(
                logos.__path__),
                                             start=1):
            # `iter_modules` yields `pkgutil.ModuleInfo` named tuple starting with Python 3.6.
            # So we manually extract the module name from `(module_finder, name, ispkg)` tuple.
            logo_module_name = logo_module_info[1]

            # Check each logo module name corresponds to a distribution identifier.
            self.assertIn(logo_module_name,
                          distributions_identifiers,
                          msg='No distribution identifier for [{0}]'.format(
                              logo_module_name))

            logo_module = lazy_load_logo_module(logo_module_name)

            # Attributes checks.
            self.assertTrue(
                getattr(logo_module, 'LOGO', []),
                msg='[{0}] logo module missing `LOGO` attribute'.format(
                    logo_module_name))
            self.assertTrue(
                getattr(logo_module, 'COLORS', []),
                msg='[{0}] logo module missing `COLORS` attribute'.format(
                    logo_module_name))

            # Make Archey compute the logo width.
            logo_width = get_logo_width(logo_module.LOGO)

            # Then, check that each logo line got the same effective width.
            for j, line in enumerate(logo_module.LOGO[1:], start=1):
                # Here we gotta trick the `get_logo_width` call.
                # We actually pass each logo line as if it was a "complete" logo.
                line_width = get_logo_width([line])

                # Width check.
                self.assertEqual(
                    line_width,
                    logo_width,
                    msg=
                    '[{0}] line index {1}, got an unexpected width {2} (expected {3})'
                    .format(logo_module_name, j, line_width, logo_width))

                # Non-empty line check.
                self.assertTrue(
                    Colors.remove_colors(line).strip(),
                    msg='[{0}] line index {1}, got an useless empty line'.
                    format(logo_module_name, j))

        # Finally, check each distributions identifier got a logo!
        # pylint: disable=undefined-loop-variable
        self.assertEqual(i,
                         len(distributions_identifiers),
                         msg='[{0}] Expected {1} logo modules, got {2}'.format(
                             logo_module_name, len(distributions_identifiers),
                             i))
예제 #20
0
    def _output_text(self):
        """
        Finally render the output entries.
        It handles text centering additionally to value and colors replacing.
        """
        # Let's copy the logo (so we don't modify the constant!)
        logo = LOGOS_DICT[self._distribution].copy()
        logo_width = get_logo_width(logo, len(self._colors_palette))

        # Let's center the entries and the logo (handles odd numbers)
        height_diff = len(logo) - len(self._results)
        if height_diff >= 0:
            self._results[0:0] = [''] * (height_diff // 2)
            self._results.extend([''] * (len(logo) - len(self._results)))
        else:
            colored_empty_line = [
                str(self._colors_palette[0]) + ' ' * logo_width
            ]
            logo[0:0] = colored_empty_line * (-height_diff // 2)
            logo.extend(colored_empty_line * (len(self._results) - len(logo)))

        text_wrapper = TextWrapper(width=(get_terminal_size().columns -
                                          logo_width),
                                   expand_tabs=False,
                                   replace_whitespace=False,
                                   drop_whitespace=False,
                                   break_on_hyphens=False,
                                   max_lines=1,
                                   placeholder='...')
        placeholder_length = len(text_wrapper.placeholder)

        # Using `TextWrapper`, shortens each entry to remove any line overlapping
        for i, entry in enumerate(self._results):
            # Shortens the entry according to the terminal width.
            # We have to remove any ANSI color, or the result would be skewed.
            wrapped_entry = text_wrapper.fill(Colors.remove_colors(entry))
            placeholder_offset = (placeholder_length if wrapped_entry.endswith(
                text_wrapper.placeholder) else 0)

            # By using previous positions, re-inserts ANSI colors back in the wrapped string.
            for color_match in ANSI_ECMA_REGEXP.finditer(entry):
                match_index = color_match.start()
                if match_index <= len(wrapped_entry) - placeholder_offset:
                    wrapped_entry = (wrapped_entry[:match_index] +
                                     color_match.group() +
                                     wrapped_entry[match_index:])

            # Add a color reset character before the placeholder (if any).
            # Rationale :
            # We cannot set `Colors.CLEAR` in the placeholder as it would skew its internals.
            if placeholder_offset:
                wrapped_entry = (wrapped_entry[:-placeholder_length] +
                                 str(Colors.CLEAR) +
                                 wrapped_entry[-placeholder_length:])

            self._results[i] = wrapped_entry

        # Merge entry results to the distribution logo.
        logo_with_entries = os.linesep.join([
            logo_part + entry_part
            for logo_part, entry_part in zip(logo, self._results)
        ])

        try:
            print(
                logo_with_entries.format(c=self._colors_palette) +
                str(Colors.CLEAR))
        except UnicodeError:
            print("""\
Your locale or TTY does not seem to support UTF-8 encoding.
Please disable Unicode within your configuration file.\
""",
                  file=sys.stderr)
예제 #21
0
    def test_distribution_logos_consistency(self):
        """
        Verify each distribution identifier got a logo module.
        Verify each distribution logo module contain `LOGO` & `COLORS` ("truthy") attributes.
        Also check they got _consistent_ widths across their respective lines.
        Additionally verify they don't contain any (useless) empty line.

        This test also indirectly checks `lazy_load_logo_module` behavior!
        """
        distributions_identifiers = Distributions.get_identifiers()

        for i, logo_module_info in enumerate(pkgutil.iter_modules(
                logos.__path__),
                                             start=1):

            # Check each logo module name corresponds to a distribution identifier.
            self.assertIn(
                logo_module_info.name,
                distributions_identifiers,
                msg=f'No distribution identifier for [{logo_module_info.name}]'
            )

            logo_module = lazy_load_logo_module(logo_module_info.name)

            # Attributes checks.
            self.assertTrue(
                getattr(logo_module, 'LOGO', []),
                msg=
                f'[{logo_module_info.name}] logo module missing `LOGO` attribute'
            )
            self.assertTrue(
                getattr(logo_module, 'COLORS', []),
                msg=
                f'[{logo_module_info.name}] logo module missing `COLORS` attribute'
            )

            # Compute once and for all the number of defined colors for this logo.
            nb_colors = len(logo_module.COLORS)

            # Make Archey compute the logo (effective) width.
            logo_width = get_logo_width(logo_module.LOGO, nb_colors)

            # Then, check that each logo line got the same effective width.
            for j, line in enumerate(logo_module.LOGO[1:], start=1):
                # Here we gotta trick the `get_logo_width` call.
                # We actually pass each logo line as if it was a "complete" logo.
                line_width = get_logo_width([line], nb_colors)

                # Width check.
                self.assertEqual(
                    line_width,
                    logo_width,
                    msg=
                    '[{}] line index {}, got an unexpected width {} (expected {})'
                    .format(logo_module_info.name, j, line_width, logo_width))

                # Non-empty line check.
                self.assertTrue(
                    Colors.remove_colors(line.format(c=[''] *
                                                     nb_colors)).strip(),
                    msg=
                    f'[{logo_module_info.name}] line index {j}, got an useless empty line'
                )

        # Finally, check each distributions identifier got a logo!
        # pylint: disable=undefined-loop-variable
        self.assertEqual(i,
                         len(distributions_identifiers),
                         msg='[{}] Expected {} logo modules, got {}'.format(
                             logo_module_info.name,
                             len(distributions_identifiers), i))
예제 #22
0
    def output(self, output):
        """
        Adds the entry to `output` after formatting with color and units.
        Follows the user configuration supplied for formatting.
        """
        # Fetch our `filesystems` object locally so we can modify it safely.
        filesystems = self.value

        if not filesystems:
            # We didn't find any disk, fall back to the default entry behavior.
            super().output(output)
            return

        # DRY configuration object for the output.
        disk_labels = self.options.get('disk_labels')
        hide_entry_name = self.options.get('hide_entry_name')

        # Combine all entries into one grand-total if configured to do so.
        if self.options.get('combine_total', True):
            name = self.name

            # Rewrite our `filesystems` object as one combining all of them.
            filesystems = {
                None: {
                    'device_path':
                    None,
                    'used_blocks':
                    sum([
                        filesystem_data['used_blocks']
                        for filesystem_data in filesystems.values()
                    ]),
                    'total_blocks':
                    sum([
                        filesystem_data['total_blocks']
                        for filesystem_data in filesystems.values()
                    ])
                }
            }
        else:
            # We will only use disk labels and entry name hiding if we aren't combining entries.
            name = ''
            # Hide `Disk` from entry name only if the user specified it... as long as a label.
            if not hide_entry_name or not disk_labels:
                name += self.name
            if disk_labels:
                if not hide_entry_name:
                    name += ' '
                name += '({disk_label})'

        # We will only run this loop a single time for combined entries.
        for mount_point, filesystem_data in filesystems.items():
            # Select the corresponding level color based on disk percentage usage.
            level_color = Colors.get_level_color(
                (filesystem_data['used_blocks'] /
                 filesystem_data['total_blocks']) * 100,
                self.options.get('warning_use_percent', 50),
                self.options.get('danger_use_percent', 75))

            # Set the correct disk label
            if disk_labels == 'mount_points':
                disk_label = mount_point
            elif disk_labels == 'device_paths':
                disk_label = filesystem_data['device_path']
            else:
                disk_label = None

            pretty_filesystem_value = f'{level_color}{{}}{Colors.CLEAR} / {{}}'.format(
                self._blocks_to_human_readable(filesystem_data['used_blocks']),
                self._blocks_to_human_readable(
                    filesystem_data['total_blocks']))

            output.append(name.format(disk_label=disk_label),
                          pretty_filesystem_value)
예제 #23
0
 def test_constant_values(self):
     """Test enumeration member instantiation from value"""
     self.assertEqual(Colors((1, 31)), Colors.RED_BRIGHT)
     self.assertRaises(ValueError, Colors, (-1, ))
예제 #24
0
    def _output_text(self) -> None:
        """
        Finally render the output entries.
        It handles text centering additionally to value and colors replacing.
        """
        # Compute the effective logo "width" from the loaded ASCII art.
        logo_width = get_logo_width(self._logo, len(self._colors))

        # Let's center the entries and the logo (handles odd numbers)
        height_diff = len(self._logo) - len(self._results)
        if height_diff >= 0:
            self._results[0:0] = [''] * (height_diff // 2)
            self._results.extend([''] * (len(self._logo) - len(self._results)))
        else:
            colored_empty_line = [str(self._colors[0]) + ' ' * logo_width]
            self._logo[0:0] = colored_empty_line * (-height_diff // 2)
            self._logo.extend(colored_empty_line *
                              (len(self._results) - len(self._logo)))

        # When writing to a pipe (for instance), prevent `TextWrapper` from truncating output.
        if not sys.stdout.isatty():
            text_width = cast(int, float("inf"))
        else:
            text_width = get_terminal_size().columns - logo_width - len(
                self.__LOGO_RIGHT_PADDING)

        text_wrapper = TextWrapper(width=text_width,
                                   expand_tabs=False,
                                   replace_whitespace=False,
                                   drop_whitespace=False,
                                   break_on_hyphens=False,
                                   max_lines=1,
                                   placeholder='...')
        placeholder_length = len(text_wrapper.placeholder)

        # Using `TextWrapper`, shortens each entry to remove any line overlapping
        for i, entry in enumerate(self._results):
            # Shortens the entry according to the terminal width.
            # We have to remove any ANSI color, or the result would be skewed.
            wrapped_entry = text_wrapper.fill(Colors.remove_colors(entry))
            placeholder_offset = (placeholder_length if wrapped_entry.endswith(
                text_wrapper.placeholder) else 0)

            # By using previous positions, re-inserts ANSI colors back in the wrapped string.
            for color_match in ANSI_ECMA_REGEXP.finditer(entry):
                match_index = color_match.start()
                if match_index <= len(wrapped_entry) - placeholder_offset:
                    wrapped_entry = (wrapped_entry[:match_index] +
                                     color_match.group() +
                                     wrapped_entry[match_index:])

            # Add a color reset character before the placeholder (if any).
            # Rationale :
            # We cannot set `Colors.CLEAR` in the placeholder as it would skew its internals.
            if placeholder_offset:
                wrapped_entry = (wrapped_entry[:-placeholder_length] +
                                 str(Colors.CLEAR) +
                                 wrapped_entry[-placeholder_length:])

            self._results[i] = wrapped_entry

        # Merge entry results to the distribution logo.
        logo_with_entries = os.linesep.join([
            f"{logo_part}{self.__LOGO_RIGHT_PADDING}{entry_part}"
            for logo_part, entry_part in zip(self._logo, self._results)
        ])

        try:
            print(logo_with_entries.format(c=self._colors) + str(Colors.CLEAR))
        except UnicodeError as unicode_error:
            raise ArcheyException("""\
Your locale or TTY does not seem to support UTF-8 encoding.
Please disable Unicode within your configuration file.\
""") from unicode_error