Coverage for klayout_pex/util/argparse_helpers.py: 89%

37 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-09-17 19:08 +0000

1#! /usr/bin/env python3 

2# 

3# -------------------------------------------------------------------------------- 

4# SPDX-FileCopyrightText: 2024-2025 Martin Jan Köhler and Harald Pretl 

5# Johannes Kepler University, Institute for Integrated Circuits. 

6# 

7# This file is part of KPEX  

8# (see https://github.com/iic-jku/klayout-pex). 

9# 

10# This program is free software: you can redistribute it and/or modify 

11# it under the terms of the GNU General Public License as published by 

12# the Free Software Foundation, either version 3 of the License, or 

13# (at your option) any later version. 

14# 

15# This program is distributed in the hope that it will be useful, 

16# but WITHOUT ANY WARRANTY; without even the implied warranty of 

17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

18# GNU General Public License for more details. 

19# 

20# You should have received a copy of the GNU General Public License 

21# along with this program. If not, see <http://www.gnu.org/licenses/>. 

22# SPDX-License-Identifier: GPL-3.0-or-later 

23# -------------------------------------------------------------------------------- 

24# 

25 

26import argparse 

27from enum import Enum, StrEnum 

28from typing import * 

29 

30from rich_argparse import RichHelpFormatter 

31 

32 

33def _group_name(name: str) -> str: 

34 """ 

35 Title-case only the group names argparse invents. 

36 

37 rich-argparse title-cases every group name, which mangles the acronyms this 

38 project uses — PEX25D would render as "Pex25D", FasterCap as "Fastercap". 

39 A title carrying an uppercase letter was written deliberately and is left 

40 alone; argparse's own all-lowercase defaults ("positional arguments", 

41 "options") still get capitalized. 

42 """ 

43 return name if any(c.isupper() for c in name) else name.title() 

44 

45 

46RichHelpFormatter.group_name_formatter = _group_name 

47 

48 

49def render_enum_help(topic: str, 

50 enum_cls: Type[Enum], 

51 print_default: bool = True, 

52 lowercase_strenum: bool = False) -> str: 

53 def canonic_string(name: str, member: str) -> str: 

54 if issubclass(enum_cls, StrEnum): 

55 if name.lower() == 'default': 

56 return 'default' 

57 return member.lower() if lowercase_strenum else member 

58 return name.lower() 

59 if not hasattr(enum_cls, 'DEFAULT'): 

60 print_default = False 

61 case_list = [f"'{canonic_string(name, member)}'" 

62 for name, member in enum_cls.__members__.items() 

63 if name.lower() != 'default'] 

64 enum_help = f"{topic} ∈ \u007b{', '.join(case_list)}\u007d" 

65 if print_default: 

66 default_case: enum_cls = getattr(enum_cls, 'DEFAULT') 

67 if issubclass(enum_cls, StrEnum): 

68 default_value: str = default_case.value 

69 if lowercase_strenum: 

70 default_value = default_value.lower() 

71 else: 

72 default_value = default_case.name.lower() 

73 enum_help += f".\nDefaults to '{default_value}'" 

74 return enum_help 

75 

76 

77def true_or_false(arg) -> bool: 

78 if isinstance(arg, bool): 

79 return arg 

80 

81 match str(arg).lower(): 

82 case 'yes' | 'true' | 't' | 'y' | 1: 

83 return True 

84 case 'no' | 'false' | 'f' | 'n' | 0: 

85 return False 

86 case _: 

87 raise argparse.ArgumentTypeError('Boolean value expected.')