absfuyu 5.6.1__py3-none-any.whl → 6.1.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of absfuyu might be problematic. Click here for more details.

Files changed (102) hide show
  1. absfuyu/__init__.py +5 -3
  2. absfuyu/__main__.py +2 -2
  3. absfuyu/cli/__init__.py +13 -2
  4. absfuyu/cli/audio_group.py +98 -0
  5. absfuyu/cli/color.py +2 -2
  6. absfuyu/cli/config_group.py +2 -2
  7. absfuyu/cli/do_group.py +2 -2
  8. absfuyu/cli/game_group.py +20 -2
  9. absfuyu/cli/tool_group.py +68 -4
  10. absfuyu/config/__init__.py +3 -3
  11. absfuyu/core/__init__.py +10 -6
  12. absfuyu/core/baseclass.py +104 -34
  13. absfuyu/core/baseclass2.py +43 -2
  14. absfuyu/core/decorator.py +2 -2
  15. absfuyu/core/docstring.py +4 -2
  16. absfuyu/core/dummy_cli.py +3 -3
  17. absfuyu/core/dummy_func.py +2 -2
  18. absfuyu/dxt/__init__.py +2 -2
  19. absfuyu/dxt/base_type.py +93 -0
  20. absfuyu/dxt/dictext.py +188 -6
  21. absfuyu/dxt/dxt_support.py +2 -2
  22. absfuyu/dxt/intext.py +72 -4
  23. absfuyu/dxt/listext.py +495 -23
  24. absfuyu/dxt/strext.py +2 -2
  25. absfuyu/extra/__init__.py +2 -2
  26. absfuyu/extra/audio/__init__.py +8 -0
  27. absfuyu/extra/audio/_util.py +57 -0
  28. absfuyu/extra/audio/convert.py +192 -0
  29. absfuyu/extra/audio/lossless.py +281 -0
  30. absfuyu/extra/beautiful.py +2 -2
  31. absfuyu/extra/da/__init__.py +39 -3
  32. absfuyu/extra/da/dadf.py +436 -29
  33. absfuyu/extra/da/dadf_base.py +2 -2
  34. absfuyu/extra/da/df_func.py +89 -5
  35. absfuyu/extra/da/mplt.py +2 -2
  36. absfuyu/extra/ggapi/__init__.py +8 -0
  37. absfuyu/extra/ggapi/gdrive.py +223 -0
  38. absfuyu/extra/ggapi/glicense.py +148 -0
  39. absfuyu/extra/ggapi/glicense_df.py +186 -0
  40. absfuyu/extra/ggapi/gsheet.py +88 -0
  41. absfuyu/extra/img/__init__.py +30 -0
  42. absfuyu/extra/img/converter.py +402 -0
  43. absfuyu/extra/img/dup_check.py +291 -0
  44. absfuyu/extra/pdf.py +4 -6
  45. absfuyu/extra/rclone.py +253 -0
  46. absfuyu/extra/xml.py +90 -0
  47. absfuyu/fun/__init__.py +2 -20
  48. absfuyu/fun/rubik.py +2 -2
  49. absfuyu/fun/tarot.py +2 -2
  50. absfuyu/game/__init__.py +2 -2
  51. absfuyu/game/game_stat.py +2 -2
  52. absfuyu/game/schulte.py +78 -0
  53. absfuyu/game/sudoku.py +2 -2
  54. absfuyu/game/tictactoe.py +2 -2
  55. absfuyu/game/wordle.py +6 -4
  56. absfuyu/general/__init__.py +2 -2
  57. absfuyu/general/content.py +2 -2
  58. absfuyu/general/human.py +2 -2
  59. absfuyu/general/resrel.py +213 -0
  60. absfuyu/general/shape.py +3 -8
  61. absfuyu/general/tax.py +344 -0
  62. absfuyu/logger.py +806 -59
  63. absfuyu/numbers/__init__.py +13 -0
  64. absfuyu/numbers/number_to_word.py +321 -0
  65. absfuyu/numbers/shorten_number.py +303 -0
  66. absfuyu/numbers/time_duration.py +217 -0
  67. absfuyu/pkg_data/__init__.py +2 -2
  68. absfuyu/pkg_data/deprecated.py +2 -2
  69. absfuyu/pkg_data/logo.py +1462 -0
  70. absfuyu/sort.py +4 -4
  71. absfuyu/tools/__init__.py +2 -2
  72. absfuyu/tools/checksum.py +119 -4
  73. absfuyu/tools/converter.py +2 -2
  74. absfuyu/tools/generator.py +24 -7
  75. absfuyu/tools/inspector.py +2 -2
  76. absfuyu/tools/keygen.py +2 -2
  77. absfuyu/tools/obfuscator.py +2 -2
  78. absfuyu/tools/passwordlib.py +2 -2
  79. absfuyu/tools/shutdownizer.py +3 -8
  80. absfuyu/tools/sw.py +213 -10
  81. absfuyu/tools/web.py +10 -13
  82. absfuyu/typings.py +5 -8
  83. absfuyu/util/__init__.py +31 -2
  84. absfuyu/util/api.py +7 -4
  85. absfuyu/util/cli.py +119 -0
  86. absfuyu/util/gui.py +91 -0
  87. absfuyu/util/json_method.py +2 -2
  88. absfuyu/util/lunar.py +2 -2
  89. absfuyu/util/package.py +124 -0
  90. absfuyu/util/path.py +313 -4
  91. absfuyu/util/performance.py +2 -2
  92. absfuyu/util/shorten_number.py +206 -13
  93. absfuyu/util/text_table.py +2 -2
  94. absfuyu/util/zipped.py +2 -2
  95. absfuyu/version.py +22 -19
  96. {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/METADATA +37 -8
  97. absfuyu-6.1.2.dist-info/RECORD +105 -0
  98. {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/WHEEL +1 -1
  99. absfuyu/extra/data_analysis.py +0 -21
  100. absfuyu-5.6.1.dist-info/RECORD +0 -79
  101. {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/entry_points.txt +0 -0
  102. {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/licenses/LICENSE +0 -0
absfuyu/dxt/listext.py CHANGED
@@ -3,10 +3,12 @@ Absfuyu: Data Extension
3
3
  -----------------------
4
4
  list extension
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
+ from __future__ import annotations
11
+
10
12
  # Module Package
11
13
  # ---------------------------------------------------------------------------
12
14
  __all__ = ["ListExt"]
@@ -20,13 +22,16 @@ from collections import Counter
20
22
  from collections.abc import Callable, Iterable
21
23
  from heapq import heapreplace
22
24
  from itertools import accumulate, chain, count, groupby, zip_longest
23
- from typing import Any, Literal, Self, cast, overload
25
+ from typing import Any, Literal, Self, TypeVar, cast, overload
24
26
 
25
27
  from absfuyu.core.baseclass import GetClassMembersMixin
26
28
  from absfuyu.core.docstring import deprecated, versionadded, versionchanged
27
- from absfuyu.typings import T as _T
28
29
  from absfuyu.util import set_min_max
29
30
 
31
+ # Type
32
+ # ---------------------------------------------------------------------------
33
+ T = TypeVar("T")
34
+ R = TypeVar("R") # Return type - Can be anything
30
35
 
31
36
  # Class
32
37
  # ---------------------------------------------------------------------------
@@ -80,9 +85,7 @@ class ListExt(GetClassMembersMixin, list):
80
85
  >>> ListExt(range(10)).head(2)
81
86
  [0, 1]
82
87
  """
83
- number_of_items = int(
84
- set_min_max(number_of_items, min_value=0, max_value=len(self))
85
- )
88
+ number_of_items = int(set_min_max(number_of_items, min_value=0, max_value=len(self)))
86
89
  return self[:number_of_items]
87
90
 
88
91
  def tail(self, number_of_items: int = 5) -> list:
@@ -106,9 +109,7 @@ class ListExt(GetClassMembersMixin, list):
106
109
  >>> ListExt(range(10)).tail(2)
107
110
  [8, 9]
108
111
  """
109
- number_of_items = int(
110
- set_min_max(number_of_items, min_value=0, max_value=len(self))
111
- )
112
+ number_of_items = int(set_min_max(number_of_items, min_value=0, max_value=len(self)))
112
113
  return self[::-1][:number_of_items][::-1]
113
114
 
114
115
  # Misc
@@ -165,15 +166,13 @@ class ListExt(GetClassMembersMixin, list):
165
166
  type_weights[type(x)] = len(type_weights)
166
167
  # logger.debug(f"Type weight: {type_weights}")
167
168
 
168
- output = sorted(
169
- lst, key=lambda x: (type_weights[type(x)], str(x)), reverse=reverse
170
- )
169
+ output = sorted(lst, key=lambda x: (type_weights[type(x)], str(x)), reverse=reverse)
171
170
 
172
171
  # logger.debug(output)
173
172
  return self.__class__(output)
174
173
 
175
174
  @overload
176
- def freq(self) -> dict: ...
175
+ def freq(self) -> dict: ... # type: ignore
177
176
 
178
177
  @overload
179
178
  def freq(
@@ -256,9 +255,7 @@ class ListExt(GetClassMembersMixin, list):
256
255
  # logger.debug(times_appear)
257
256
 
258
257
  if appear_increment:
259
- times_appear_increment: list[int] = list(
260
- accumulate(times_appear.values(), operator.add)
261
- )
258
+ times_appear_increment: list[int] = list(accumulate(times_appear.values(), operator.add))
262
259
  # logger.debug(times_appear_increment)
263
260
  return times_appear_increment # incremental index list
264
261
  else:
@@ -385,7 +382,7 @@ class ListExt(GetClassMembersMixin, list):
385
382
  return self.__class__(zip(nums, self))
386
383
 
387
384
  @versionadded("5.3.0") # no test case yet
388
- def transpose(self, fillvalue: _T | None = None, /) -> Self:
385
+ def transpose(self, fillvalue: Any | None = None, /) -> Self:
389
386
  """
390
387
  Transpose a list of iterable.
391
388
 
@@ -459,6 +456,11 @@ class ListExt(GetClassMembersMixin, list):
459
456
  """
460
457
  return [self.pick_one() for _ in range(number_of_items)]
461
458
 
459
+ @versionadded("5.10.0")
460
+ def shuffle(self) -> Self:
461
+ random.shuffle(self)
462
+ return self
463
+
462
464
  # Len
463
465
  @versionchanged("5.2.0", reason="Handle more type")
464
466
  def len_items(self) -> Self:
@@ -746,7 +748,7 @@ class ListExt(GetClassMembersMixin, list):
746
748
  return self.slice_points(slice_points)
747
749
 
748
750
  @versionadded("5.3.0") # no test case yet
749
- def to_column(self, ncols: int, fillvalue: _T | None = None) -> Self:
751
+ def to_column(self, ncols: int, fillvalue: Any | None = None) -> Self:
750
752
  """
751
753
  Smart convert 1 dimension list to 2 dimension list,
752
754
  in which, number of columns = ``ncols``.
@@ -756,7 +758,7 @@ class ListExt(GetClassMembersMixin, list):
756
758
  ncols : int
757
759
  Number of columns
758
760
 
759
- fillvalue : T | None, optional
761
+ fillvalue : Any | None, optional
760
762
  Fill value, by default ``None``
761
763
 
762
764
  Returns
@@ -857,9 +859,7 @@ class ListExt(GetClassMembersMixin, list):
857
859
  fill = " "
858
860
 
859
861
  # Calculate how many columns of text
860
- column_count = (
861
- max(1, available_width // max_item_length) if max_item_length > 0 else 1
862
- )
862
+ column_count = max(1, available_width // max_item_length) if max_item_length > 0 else 1
863
863
 
864
864
  # splitted_chunk: list[list[str]] = self.split_chunk(cols)
865
865
  # mod_chunk = self.__class__(
@@ -876,3 +876,475 @@ class ListExt(GetClassMembersMixin, list):
876
876
  mod_chunk = self.split_chunk(column_count).apply(mod_item)
877
877
 
878
878
  return mod_chunk
879
+
880
+
881
+ class ListExt2(GetClassMembersMixin, list[T]):
882
+ """
883
+ ``list`` extension (with generic - W.I.P)
884
+
885
+ >>> # For a list of new methods
886
+ >>> ListExt2.show_all_methods()
887
+ """
888
+
889
+ # MARK: Info
890
+ def head(self, number_of_items: int = 5, /) -> list[T]:
891
+ """
892
+ Show first ``number_of_items`` items in ``ListExt``
893
+
894
+ Parameters
895
+ ----------
896
+ number_of_items : int
897
+ | Number of items to shows at once
898
+ | (Default: ``5``)
899
+
900
+ Returns
901
+ -------
902
+ list
903
+ Filtered list
904
+
905
+
906
+ Example:
907
+ --------
908
+ >>> ListExt(range(10)).head(2)
909
+ [0, 1]
910
+ """
911
+ number_of_items = int(set_min_max(number_of_items, min_value=0, max_value=len(self)))
912
+ return self[:number_of_items]
913
+
914
+ def tail(self, number_of_items: int = 5, /) -> list[T]:
915
+ """
916
+ Show last ``number_of_items`` items in ``ListExt``
917
+
918
+ Parameters
919
+ ----------
920
+ number_of_items : int
921
+ | Number of items to shows at once
922
+ | (Default: ``5``)
923
+
924
+ Returns
925
+ -------
926
+ list
927
+ Filtered list
928
+
929
+
930
+ Example:
931
+ --------
932
+ >>> ListExt(range(10)).tail(2)
933
+ [8, 9]
934
+ """
935
+ number_of_items = int(set_min_max(number_of_items, min_value=0, max_value=len(self)))
936
+ return self[::-1][:number_of_items][::-1]
937
+
938
+ # MARK: Misc
939
+ def apply(self, func: Callable[[T], R]) -> ListExt2[R]:
940
+ """
941
+ Apply function to each entry
942
+
943
+ Parameters
944
+ ----------
945
+ func : Callable[[Any], Any]
946
+ Callable function
947
+
948
+ Returns
949
+ -------
950
+ Self
951
+ ListExt
952
+
953
+
954
+ Example:
955
+ --------
956
+ >>> test = ListExt([1, 2, 3])
957
+ >>> test.apply(str)
958
+ ['1', '2', '3']
959
+ """
960
+ # return self.__class__(map(func, self))
961
+ return self.__class__(func(x) for x in self) # type: ignore
962
+
963
+ def sorts(self, reverse: bool = False) -> Self:
964
+ """
965
+ Sort all items (with different type) in ``list``
966
+
967
+ Parameters
968
+ ----------
969
+ reverse : bool
970
+ - ``True`` then sort in descending order
971
+ - ``False`` then sort in ascending order (default value)
972
+
973
+ Returns
974
+ -------
975
+ Self
976
+ A sorted list
977
+
978
+
979
+ Example:
980
+ --------
981
+ >>> test = ListExt([9, "abc", 3.5, "aaa", 1, 1.4])
982
+ >>> test.sorts()
983
+ [1, 9, 'aaa', 'abc', 1.4, 3.5]
984
+ """
985
+ lst = self.copy()
986
+ type_weights: dict = {}
987
+ for x in lst:
988
+ if type(x) not in type_weights:
989
+ type_weights[type(x)] = len(type_weights)
990
+ # logger.debug(f"Type weight: {type_weights}")
991
+
992
+ output = sorted(lst, key=lambda x: (type_weights[type(x)], str(x)), reverse=reverse)
993
+
994
+ # logger.debug(output)
995
+ return self.__class__(output)
996
+
997
+ @versionchanged("5.2.0", reason="New ``recursive`` parameter")
998
+ def flatten(self, recursive: bool = False) -> Self:
999
+ """
1000
+ Flatten the list
1001
+
1002
+ Parameters
1003
+ ----------
1004
+ recursive : bool
1005
+ Recursively flatten the list, by default ``False``
1006
+
1007
+ Returns
1008
+ -------
1009
+ Self
1010
+ Flattened list
1011
+
1012
+
1013
+ Example:
1014
+ --------
1015
+ >>> test = ListExt([["test"], ["test", "test"], ["test"]])
1016
+ >>> test.flatten()
1017
+ ['test', 'test', 'test', 'test']
1018
+ """
1019
+
1020
+ def instance_checking(item):
1021
+ if isinstance(item, str):
1022
+ return [item]
1023
+ if isinstance(item, Iterable):
1024
+ return item
1025
+ return [item]
1026
+
1027
+ # Flatten
1028
+ list_of_list = (instance_checking(x) for x in self)
1029
+ flattened = self.__class__(chain(*list_of_list))
1030
+
1031
+ # Recursive
1032
+ if recursive:
1033
+
1034
+ def _condition(item) -> bool:
1035
+ if isinstance(item, str):
1036
+ return False
1037
+ return isinstance(item, Iterable)
1038
+
1039
+ while any(flattened.apply(_condition)):
1040
+ flattened = flattened.flatten()
1041
+
1042
+ # Return
1043
+ return flattened
1044
+
1045
+ @versionadded("5.2.0")
1046
+ def join(self, sep: str = " ", /) -> str:
1047
+ """
1048
+ Join every element in list to str (``str.join()`` wrapper).
1049
+
1050
+ Parameters
1051
+ ----------
1052
+ sep : str, optional
1053
+ Separator between each element, by default ``" "``
1054
+
1055
+ Returns
1056
+ -------
1057
+ str
1058
+ Joined list
1059
+
1060
+
1061
+ Example:
1062
+ --------
1063
+ >>> ListExt(["a", "b", "c"]).join()
1064
+ a b c
1065
+
1066
+ >>> # Also work with non-str type
1067
+ >>> ListExt([1, 2, 3]).join()
1068
+ 1 2 3
1069
+ """
1070
+ try:
1071
+ return sep.join(self) # type: ignore
1072
+ except TypeError:
1073
+ return sep.join(self.apply(str))
1074
+
1075
+ def group_by_pair_value(self, max_loop: int = 3):
1076
+ """
1077
+ Assume each ``list`` in ``list`` is a pair value,
1078
+ returns a ``list`` contain all paired value
1079
+
1080
+ Parameters
1081
+ ----------
1082
+ max_loop : int
1083
+ Times to run functions (minimum: ``3``)
1084
+
1085
+ Returns
1086
+ -------
1087
+ list[list]
1088
+ Grouped value
1089
+
1090
+
1091
+ Example:
1092
+ --------
1093
+ >>> test = ListExt([[1, 2], [2, 3], [4, 3], [5, 6]])
1094
+ >>> test.group_by_pair_value()
1095
+ [[1, 2, 3, 4], [5, 6]]
1096
+
1097
+ >>> test = ListExt([[8, 3], [4, 6], [6, 3], [5, 2], [7, 2]])
1098
+ >>> test.group_by_pair_value()
1099
+ [[8, 3, 4, 6], [2, 5, 7]]
1100
+
1101
+ >>> test = ListExt([["a", 4], ["b", 4], [5, "c"]])
1102
+ >>> test.group_by_pair_value()
1103
+ [['a', 4, 'b'], ['c', 5]]
1104
+ """
1105
+
1106
+ iter = self.copy()
1107
+
1108
+ # Init loop
1109
+ for _ in range(max(max_loop, 3)):
1110
+ temp: dict[Any, list] = {}
1111
+ # Make dict{key: all `item` that contains `key`}
1112
+ for item in iter:
1113
+ for x in item:
1114
+ if temp.get(x, None) is None:
1115
+ temp[x] = [item]
1116
+ else:
1117
+ temp[x].append(item)
1118
+
1119
+ # Flatten dict.values
1120
+ temp = {k: list(set(chain(*v))) for k, v in temp.items()}
1121
+
1122
+ iter = list(temp.values())
1123
+
1124
+ return list(x for x, _ in groupby(iter))
1125
+
1126
+ def split_equal(self, n: int, sort: bool = True) -> ListExt2[list[T]]:
1127
+ """
1128
+ Try to equally split a list of number into ``n`` equal sum parts.
1129
+
1130
+ **Note:** Element in list must be a number.
1131
+
1132
+ Parameters
1133
+ ----------
1134
+ n : int
1135
+ Split into how many parts. Must be >= 1.
1136
+
1137
+ sort : bool, optional
1138
+ Sort the instance before split, by default ``True``
1139
+
1140
+ Returns
1141
+ -------
1142
+ Self
1143
+ Splitted (list[list])
1144
+
1145
+
1146
+ Example:
1147
+ --------
1148
+ >>> ListExt(range(1, 11)).split_equal(2)
1149
+ [[10, 7, 5, 4, 1], [9, 8, 6, 3, 2]]
1150
+ """
1151
+
1152
+ # https://stackoverflow.com/a/61649667
1153
+ bins: list[list[int]] = [[0] for _ in range(max(n, 1))]
1154
+ if sort:
1155
+ # self = self.sorts(reverse=True)
1156
+ self = self.__class__(sorted(self, reverse=True))
1157
+ for x in self:
1158
+ least = bins[0]
1159
+ least[0] += x
1160
+ least.append(x)
1161
+ heapreplace(bins, least)
1162
+ return self.__class__(x[1:] for x in bins)
1163
+
1164
+ # MARK: Random
1165
+ def pick_one(self) -> T:
1166
+ """
1167
+ Pick one random items from ``list``
1168
+
1169
+ Returns
1170
+ -------
1171
+ Any
1172
+ Random value
1173
+
1174
+
1175
+ Example:
1176
+ --------
1177
+ >>> test = ListExt(["foo", "bar"])
1178
+ >>> test.pick_one()
1179
+ 'bar'
1180
+ """
1181
+ if len(self) != 0:
1182
+ out = random.choice(self)
1183
+ # logger.debug(out)
1184
+ return out
1185
+ else:
1186
+ # logger.debug("List empty!")
1187
+ raise IndexError("List empty!")
1188
+
1189
+ def get_random(self, number_of_items: int = 5, /) -> list[T]:
1190
+ """
1191
+ Get ``number_of_items`` random items in ``ListExt``
1192
+
1193
+ Parameters
1194
+ ----------
1195
+ number_of_items : int
1196
+ Number random of items, by default ``5``
1197
+
1198
+ Returns
1199
+ -------
1200
+ list
1201
+ Filtered list
1202
+ """
1203
+ return [self.pick_one() for _ in range(number_of_items)]
1204
+
1205
+ @versionadded("5.10.0")
1206
+ def shuffle(self) -> Self:
1207
+ """
1208
+ Shuffle the list itself
1209
+
1210
+ Returns
1211
+ -------
1212
+ Self
1213
+ Shuffled list
1214
+ """
1215
+ random.shuffle(self)
1216
+ return self
1217
+
1218
+ # MARK: Len
1219
+ @versionchanged("5.2.0", reason="Handle more type")
1220
+ def len_items(self) -> ListExt2[int]:
1221
+ """
1222
+ ``len()`` for every item in ``self``
1223
+
1224
+ Returns
1225
+ -------
1226
+ Self
1227
+ List of ``len()``'ed value
1228
+
1229
+
1230
+ Example:
1231
+ --------
1232
+ >>> test = ListExt(["foo", "bar", "pizza"])
1233
+ >>> test.len_items()
1234
+ [3, 3, 5]
1235
+ """
1236
+
1237
+ def _len(item: Any) -> int:
1238
+ try:
1239
+ return len(item)
1240
+ except TypeError:
1241
+ return len(str(item))
1242
+
1243
+ return self.__class__([_len(x) for x in self]) # type: ignore
1244
+
1245
+ @versionchanged("5.2.0", reason="New ``recursive`` parameter")
1246
+ def mean_len(self, recursive: bool = False) -> float:
1247
+ """
1248
+ Average length of every item. Returns zero if failed (empty list, ZeroDivisionError).
1249
+
1250
+ Parameters
1251
+ ----------
1252
+ recursive : bool
1253
+ Recursively find the average length of items in nested lists, by default ``False``
1254
+
1255
+ Returns
1256
+ -------
1257
+ float
1258
+ Average length
1259
+
1260
+
1261
+ Example:
1262
+ --------
1263
+ >>> test = ListExt(["foo", "bar", "pizza"])
1264
+ >>> test.mean_len()
1265
+ 3.6666666666666665
1266
+ """
1267
+
1268
+ if recursive:
1269
+ dat = self.flatten(recursive=recursive)
1270
+ else:
1271
+ dat = self
1272
+
1273
+ try:
1274
+ return sum(dat.len_items()) / len(dat)
1275
+ except ZeroDivisionError:
1276
+ return 0.0
1277
+
1278
+ @versionadded("5.2.0")
1279
+ def max_item_len(self, recursive: bool = False) -> int:
1280
+ """
1281
+ Find the maximum length of items in the list.
1282
+
1283
+ Parameters
1284
+ ----------
1285
+ recursive : bool
1286
+ Recursively find the maximum length of items in nested lists, by default ``False``
1287
+
1288
+ Returns
1289
+ -------
1290
+ int
1291
+ Maximum length of items
1292
+
1293
+
1294
+ Example:
1295
+ --------
1296
+ >>> test = ListExt(["test", "longer_test"])
1297
+ >>> test.max_item_len()
1298
+ 11
1299
+
1300
+ >>> test = ListExt([["short"], ["longer_test"]])
1301
+ >>> test.max_item_len(recursive=True)
1302
+ 11
1303
+ """
1304
+ if recursive:
1305
+ return cast(int, max(self.flatten(recursive=True).len_items()))
1306
+ return cast(int, max(self.len_items()))
1307
+
1308
+ # MARK: Group/Unique
1309
+ def unique(self) -> Self:
1310
+ """
1311
+ Remove duplicates
1312
+
1313
+ Returns
1314
+ -------
1315
+ Self
1316
+ Duplicates removed list
1317
+
1318
+
1319
+ Example:
1320
+ --------
1321
+ >>> test = ListExt([1, 1, 1, 2, 2, 3])
1322
+ >>> test.unique()
1323
+ [1, 2, 3]
1324
+ """
1325
+ return self.__class__(set(self))
1326
+
1327
+ def group_by_unique(self) -> ListExt2[list[T]]:
1328
+ """
1329
+ Group duplicated elements into list
1330
+
1331
+ Returns
1332
+ -------
1333
+ Self
1334
+ Grouped value
1335
+
1336
+
1337
+ Example:
1338
+ --------
1339
+ >>> test = ListExt([1, 2, 3, 1, 3, 3, 2])
1340
+ >>> test.group_by_unique()
1341
+ [[1, 1], [2, 2], [3, 3, 3]]
1342
+ """
1343
+ temp = groupby(self.sorts())
1344
+ return self.__class__([list(g) for _, g in temp])
1345
+
1346
+
1347
+ if __name__ == "__main__":
1348
+ test = ListExt2([1, 2, 3, 4, 5, "s"])
1349
+ a = test.group_by_unique()
1350
+ print(a, type(a))
absfuyu/dxt/strext.py CHANGED
@@ -3,8 +3,8 @@ Absfuyu: Data Extension
3
3
  -----------------------
4
4
  str extension
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module Package
absfuyu/extra/__init__.py CHANGED
@@ -3,8 +3,8 @@ Absfuyu: Extra
3
3
  --------------
4
4
  Features that require additional libraries
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
 
@@ -0,0 +1,8 @@
1
+ """
2
+ Absfuyu: Audio
3
+ --------------
4
+ Audio related
5
+
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
+ """
@@ -0,0 +1,57 @@
1
+ """
2
+ Absfuyu: Audio
3
+ --------------
4
+ Audio convert, lossless checker
5
+
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
+ """
9
+
10
+ # Module level
11
+ # ---------------------------------------------------------------------------
12
+ __all__ = ["StatusCode", "ResultStatus"]
13
+
14
+ # Library
15
+ # ---------------------------------------------------------------------------
16
+ from enum import StrEnum
17
+ from pathlib import Path
18
+
19
+ # Class Enum, Result
20
+ # ---------------------------------------------------------------------------
21
+ try:
22
+ from rich import print
23
+
24
+ class StatusCode(StrEnum):
25
+ OK = "[bold green]OK[/]"
26
+ SKIP = "[bold yellow]SKIP[/]"
27
+ ERROR = "[bold red]ERROR[/]"
28
+ LOSSLESS = "[bold green]LOSSLESS[/]"
29
+ NOT_LOSSLESS = "[bold red]NOT LOSSLESS[/]"
30
+ HIRES = "[bold blue]HIRES[/]"
31
+
32
+ except ImportError:
33
+
34
+ class StatusCode(StrEnum):
35
+ OK = "OK"
36
+ SKIP = "SKIP"
37
+ ERROR = "ERROR"
38
+ LOSSLESS = "LOSSLESS"
39
+ NOT_LOSSLESS = "NOT LOSSLESS"
40
+ HIRES = "HIRES"
41
+
42
+
43
+ class ResultStatus:
44
+ """
45
+ Result status
46
+ """
47
+
48
+ def __init__(self, status: StatusCode, path: Path) -> None:
49
+ self.status = status
50
+ self.path = path
51
+
52
+ def __repr__(self) -> str:
53
+ return f"{self.status} : {self.path.name}"
54
+
55
+ def print(self) -> None:
56
+ """Print repr (for rich package)"""
57
+ print(self.status, ": ", self.path.name)