From 16132e6fc9d54f237f260227f99dad5b639891db Mon Sep 17 00:00:00 2001 From: Daniel Girtler Date: Wed, 21 Jun 2023 17:52:06 +1000 Subject: Fix 1862 (#1884) * Fix 1862 * Update --------- Co-authored-by: Daniel Girtler --- archinstall/lib/disk/device_model.py | 41 +++++++-- archinstall/lib/disk/partitioning_menu.py | 117 ++++++++++++++---------- archinstall/lib/mirrors.py | 2 +- archinstall/lib/models/network_configuration.py | 2 +- archinstall/lib/output.py | 20 ++-- 5 files changed, 116 insertions(+), 66 deletions(-) diff --git a/archinstall/lib/disk/device_model.py b/archinstall/lib/disk/device_model.py index 36dd0c4f..8e72390c 100644 --- a/archinstall/lib/disk/device_model.py +++ b/archinstall/lib/disk/device_model.py @@ -137,6 +137,10 @@ class Unit(Enum): Percent = '%' # size in percentile + @staticmethod + def get_all_units() -> List[str]: + return [u.name for u in Unit] + @dataclass class Size: @@ -214,16 +218,25 @@ class Size: value = int(self._normalize() / target_unit.value) # type: ignore return Size(value, target_unit) + def as_text(self) -> str: + return self.format_size( + self.unit, + self.sector_size + ) + def format_size( self, target_unit: Unit, - sector_size: Optional[Size] = None + sector_size: Optional[Size] = None, + include_unit: bool = True ) -> str: if self.unit == Unit.Percent: return f'{self.value}%' else: target_size = self.convert(target_unit, sector_size) - return f'{target_size.value} {target_unit.name}' + if include_unit: + return f'{target_size.value} {target_unit.name}' + return f'{target_size.value}' def _normalize(self) -> int: """ @@ -280,7 +293,7 @@ class _PartitionInfo: mountpoints: List[Path] btrfs_subvol_infos: List[_BtrfsSubvolumeInfo] = field(default_factory=list) - def as_json(self) -> Dict[str, Any]: + def table_data(self) -> Dict[str, Any]: part_info = { 'Name': self.name, 'Type': self.type.value, @@ -343,7 +356,7 @@ class _DeviceInfo: read_only: bool dirty: bool - def as_json(self) -> Dict[str, Any]: + def table_data(self) -> Dict[str, Any]: total_free_space = sum([region.get_length(unit=Unit.MiB) for region in self.free_space_regions]) return { 'Model': self.model, @@ -440,7 +453,7 @@ class SubvolumeModification: 'nodatacow': self.nodatacow } - def as_json(self) -> Dict[str, Any]: + def table_data(self) -> Dict[str, Any]: return { 'name': str(self.name), 'mountpoint': str(self.mountpoint), @@ -465,12 +478,20 @@ class DeviceGeometry: def get_length(self, unit: Unit = Unit.sectors) -> int: return self._geometry.getLength(unit.name) - def as_json(self) -> Dict[str, Any]: + def table_data(self) -> Dict[str, Any]: + start = Size(self._geometry.start, Unit.sectors, self._sector_size) + end = Size(self._geometry.end, Unit.sectors, self._sector_size) + length = Size(self._geometry.getLength(), Unit.sectors, self._sector_size) + + start_str = f'{self._geometry.start} / {start.format_size(Unit.B, include_unit=False)}' + end_str = f'{self._geometry.end} / {end.format_size(Unit.B, include_unit=False)}' + length_str = f'{self._geometry.getLength()} / {length.format_size(Unit.B, include_unit=False)}' + return { 'Sector size': self._sector_size.value, - 'Start sector': self._geometry.start, - 'End sector': self._geometry.end, - 'Length': self._geometry.getLength() + 'Start (sector/B)': start_str, + 'End (sector/B)': end_str, + 'Length (sectors/B)': length_str } @@ -700,7 +721,7 @@ class PartitionModification: 'btrfs': [vol.__dump__() for vol in self.btrfs_subvols] } - def as_json(self) -> Dict[str, Any]: + def table_data(self) -> Dict[str, Any]: """ Called for displaying data in table format """ diff --git a/archinstall/lib/disk/partitioning_menu.py b/archinstall/lib/disk/partitioning_menu.py index 89cf6293..4acb4e85 100644 --- a/archinstall/lib/disk/partitioning_menu.py +++ b/archinstall/lib/disk/partitioning_menu.py @@ -1,10 +1,11 @@ from __future__ import annotations +import re from pathlib import Path from typing import Any, Dict, TYPE_CHECKING, List, Optional, Tuple from .device_model import PartitionModification, FilesystemType, BDevice, Size, Unit, PartitionType, PartitionFlag, \ - ModificationStatus + ModificationStatus, DeviceGeometry from ..menu import Menu, ListManager, MenuSelection, TextInput from ..output import FormattedOutput, warn from .subvolume_menu import SubvolumeMenu @@ -192,22 +193,51 @@ class PartitioningList(ListManager): choice = Menu(prompt, options, sort=False, skip=False).run() return options[choice.single_value] - def _validate_sector(self, start_sector: str, end_sector: Optional[str] = None) -> bool: - if not start_sector.isdigit(): - return False + def _validate_value( + self, + sector_size: Size, + total_size: Size, + value: str + ) -> Optional[Size]: + match = re.match(r'([0-9]+)([a-zA-Z|%]*)', value, re.I) + + if match: + value, unit = match.groups() + + if unit == '%': + unit = Unit.Percent.name + + if unit and unit not in Unit.get_all_units(): + return None + + unit = Unit[unit] if unit else Unit.sectors + return Size(int(value), unit, sector_size, total_size) + + return None + + def _enter_size( + self, + sector_size: Size, + total_size: Size, + prompt: str, + default: Size + ) -> Size: + while True: + value = TextInput(prompt).run().strip() + + size: Optional[Size] = None - if end_sector: - if end_sector.endswith('%'): - if not end_sector[:-1].isdigit(): - return False - elif not end_sector.isdigit(): - return False - elif int(start_sector) > int(end_sector): - return False + if not value: + size = default + else: + size = self._validate_value(sector_size, total_size, value) - return True + if size: + return size - def _prompt_sectors(self) -> Tuple[Size, Size]: + warn(f'Invalid value: {value}') + + def _prompt_size(self) -> Tuple[Size, Size]: device_info = self._device.device_info text = str(_('Current free sectors on device {}:')).format(device_info.path) + '\n\n' @@ -215,54 +245,45 @@ class PartitioningList(ListManager): prompt = text + free_space_table + '\n' total_sectors = device_info.total_size.format_size(Unit.sectors, device_info.sector_size) - prompt += str(_('Total sectors: {}')).format(total_sectors) + '\n' + total_bytes = device_info.total_size.format_size(Unit.B) + + prompt += str(_('Total: {} / {}')).format(total_sectors, total_bytes) + '\n\n' + prompt += str(_('All entered values can be suffixed with a unit: B, KB, KiB, MB, MiB...')) + '\n' + prompt += str(_('If no unit is provided, the value is interpreted as sectors')) + '\n' print(prompt) - largest_free_area = max(device_info.free_space_regions, key=lambda r: r.get_length()) + largest_free_area: DeviceGeometry = max(device_info.free_space_regions, key=lambda r: r.get_length()) # prompt until a valid start sector was entered - while True: - start_prompt = str(_('Enter the start sector (default: {}): ')).format(largest_free_area.start) - start_sector = TextInput(start_prompt).run().strip() - - if not start_sector or self._validate_sector(start_sector): - break - - warn(f'Invalid start sector entered: {start_sector}') + default_start = Size(largest_free_area.start, Unit.sectors, device_info.sector_size) + start_prompt = str(_('Enter start (default: sector {}): ')).format(largest_free_area.start) + start_size = self._enter_size( + device_info.sector_size, + device_info.total_size, + start_prompt, + default_start + ) - if not start_sector: - start_sector = str(largest_free_area.start) - end_sector = str(largest_free_area.end) + if start_size.value == largest_free_area.start: + end_size = Size(largest_free_area.end, Unit.sectors, device_info.sector_size) else: - end_sector = '100%' + end_size = Size(100, Unit.Percent, total_size=device_info.total_size) # prompt until valid end sector was entered - while True: - end_prompt = str(_('Enter the end sector of the partition (percentage or block number, default: {}): ')).format(end_sector) - end_value = TextInput(end_prompt).run().strip() - - if not end_value or self._validate_sector(start_sector, end_value): - break - - warn(f'Invalid end sector entered: {start_sector}') - - # override the default value with the user value - if end_value: - end_sector = end_value - - start_size = Size(int(start_sector), Unit.sectors, device_info.sector_size) - - if end_sector.endswith('%'): - end_size = Size(int(end_sector[:-1]), Unit.Percent, device_info.sector_size, device_info.total_size) - else: - end_size = Size(int(end_sector), Unit.sectors, device_info.sector_size) + end_prompt = str(_('Enter end (default: {}): ')).format(end_size.as_text()) + end_size = self._enter_size( + device_info.sector_size, + device_info.total_size, + end_prompt, + end_size + ) return start_size, end_size def _create_new_partition(self) -> PartitionModification: fs_type = self._prompt_partition_fs_type() - start_size, end_size = self._prompt_sectors() + start_size, end_size = self._prompt_size() length = end_size - start_size # new line for the next prompt diff --git a/archinstall/lib/mirrors.py b/archinstall/lib/mirrors.py index 521a8e5b..74cdd0aa 100644 --- a/archinstall/lib/mirrors.py +++ b/archinstall/lib/mirrors.py @@ -30,7 +30,7 @@ class CustomMirror: sign_check: SignCheck sign_option: SignOption - def as_json(self) -> Dict[str, str]: + def table_data(self) -> Dict[str, str]: return { 'Name': self.name, 'Url': self.url, diff --git a/archinstall/lib/models/network_configuration.py b/archinstall/lib/models/network_configuration.py index 93dd1c44..e564b97b 100644 --- a/archinstall/lib/models/network_configuration.py +++ b/archinstall/lib/models/network_configuration.py @@ -39,7 +39,7 @@ class NetworkConfiguration: else: return 'Unknown type' - def as_json(self) -> Dict: + def table_data(self) -> Dict[str, Any]: exclude_fields = ['type'] data = {} for k, v in self.__dict__.items(): diff --git a/archinstall/lib/output.py b/archinstall/lib/output.py index d266afa8..d1c95ec5 100644 --- a/archinstall/lib/output.py +++ b/archinstall/lib/output.py @@ -11,14 +11,16 @@ from .storage import storage class FormattedOutput: + @classmethod - def values( + def _get_values( cls, o: Any, class_formatter: Optional[Union[str, Callable]] = None, filter_list: List[str] = [] ) -> Dict[str, Any]: - """ the original values returned a dataclass as dict thru the call to some specific methods + """ + the original values returned a dataclass as dict thru the call to some specific methods this version allows thru the parameter class_formatter to call a dynamicly selected formatting method. Can transmit a filter list to the class_formatter, """ @@ -33,8 +35,8 @@ class FormattedOutput: return func(filter_list) raise ValueError('Unsupported formatting call') - elif hasattr(o, 'as_json'): - return o.as_json() + elif hasattr(o, 'table_data'): + return o.table_data() elif hasattr(o, 'json'): return o.json() elif is_dataclass(o): @@ -58,7 +60,7 @@ class FormattedOutput: is for compatibility with a print statement As_table_filter can be a drop in replacement for as_table """ - raw_data = [cls.values(o, class_formatter, filter_list) for o in obj] + raw_data = [cls._get_values(o, class_formatter, filter_list) for o in obj] # determine the maximum column size column_width: Dict[str, int] = {} @@ -92,18 +94,24 @@ class FormattedOutput: for key in filter_list: width = column_width.get(key, len(key)) value = record.get(key, '') + if '!' in key: value = '*' * width - if isinstance(value,(int, float)) or (isinstance(value, str) and value.isnumeric()): + + if isinstance(value, (int, float)) or (isinstance(value, str) and value.isnumeric()): obj_data.append(str(value).rjust(width)) else: obj_data.append(str(value).ljust(width)) + output += ' | '.join(obj_data) + '\n' return output @classmethod def as_columns(cls, entries: List[str], cols: int) -> str: + """ + Will format a list into a given number of columns + """ chunks = [] output = '' -- cgit v1.2.3-54-g00ecf