tksheet 7.4.2__py3-none-any.whl → 7.4.4__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.
tksheet/sorting.py CHANGED
@@ -3,12 +3,12 @@ from __future__ import annotations
3
3
  from collections.abc import Callable, Generator, Iterable, Iterator
4
4
  from datetime import datetime
5
5
  from pathlib import Path
6
- from re import finditer
6
+ from re import split
7
7
 
8
8
  AnyIter = Iterable | Iterator
9
9
 
10
10
  # Possible date formats to try for the entire string
11
- date_formats = [
11
+ date_formats = (
12
12
  # Common formats
13
13
  "%d/%m/%Y", # Day/Month/Year
14
14
  "%m/%d/%Y", # Month/Day/Year (US format)
@@ -24,7 +24,8 @@ date_formats = [
24
24
  "%m,%d,%Y", # Month,Day,Year
25
25
  "%Y,%m,%d", # Year,Month,Day
26
26
  "%d %m %Y", # Day Month Year (with space)
27
- "%m %d %Y", # Month Day Year
27
+ "%m %d %Y", # Month Day Year (with space)
28
+ "%Y %d %m", # Year Month Day (with space)
28
29
  # With month names
29
30
  "%d %b %Y", # Day Abbreviated Month Year
30
31
  "%b %d, %Y", # Abbreviated Month Day, Year
@@ -42,76 +43,228 @@ date_formats = [
42
43
  "%d/%m/%y %H:%M", # Day/Month/Year Hour:Minute
43
44
  "%m/%d/%y %H:%M", # Month/Day/Year Hour:Minute
44
45
  "%Y-%m-%d %H:%M:%S", # Year-Month-Day Hour:Minute:Second
45
- ]
46
+ )
46
47
 
47
48
 
48
- def natural_sort_key(
49
- item: object,
50
- ) -> tuple[int, ...]:
49
+ def _string_fallback(item: str) -> tuple[int, ...]:
50
+ """
51
+ In order to have reasonable file path sorting:
52
+ - Split by path separators
53
+ - Determine depth, more separators more depth
54
+ - Deal with every dir by splitting by digits
55
+ - Deal with file name by splitting by digits
56
+ """
57
+ components = split(r"[/\\]", item)
58
+ if components[-1]:
59
+ return (
60
+ 5,
61
+ len(components),
62
+ tuple(int(e) if e.isdigit() else e.lower() for comp in components[:-1] for e in split(r"(\d+)", comp) if e),
63
+ tuple(int(e) if e.isdigit() else e.lower() for e in split(r"(\d+)", components[-1])),
64
+ )
65
+ else:
66
+ return (
67
+ 5,
68
+ len(components),
69
+ tuple(int(e) if e.isdigit() else e.lower() for comp in components[:-1] for e in split(r"(\d+)", comp) if e),
70
+ tuple(),
71
+ )
72
+
73
+
74
+ def version_sort_key(item: object) -> tuple[int, ...]:
51
75
  """
52
76
  A key for natural sorting of various Python types.
53
77
 
54
- This function returns a tuple where the first element is an integer
55
- ranking the type, followed by type-specific comparison values.
78
+ - Won't convert strings to floats
79
+ - Will sort string version numbers
56
80
 
57
- 1. None
81
+ 0. None
82
+ 1. Empty strings
58
83
  2. bool
59
84
  3. int, float
60
- 4. datetime
61
- 5. filepaths
62
- 6. empty strings
63
- 7. strings
64
- 8. unknown objects with __str__ method
65
- 9. unknown objects
66
-
67
- :param item: Any Python object to be sorted.
68
- :return: A tuple for sorting, with type indicator and comparison values.
85
+ 4. datetime (inc. strings that are dates)
86
+ 5. strings (including string file paths and paths as POSIX strings) & unknown objects with __str__
87
+ 6. unknown objects
69
88
  """
70
- if item is None:
89
+ if isinstance(item, str):
90
+ if item:
91
+ for date_format in date_formats:
92
+ try:
93
+ return (4, datetime.strptime(item, date_format).timestamp())
94
+ except ValueError:
95
+ continue
96
+ # the same as _string_fallback
97
+ components = split(r"[/\\]", item)
98
+ if components[-1]:
99
+ return (
100
+ 5,
101
+ len(components),
102
+ tuple(
103
+ int(e) if e.isdigit() else e.lower()
104
+ for comp in components[:-1]
105
+ for e in split(r"(\d+)", comp)
106
+ if e
107
+ ),
108
+ tuple(int(e) if e.isdigit() else e.lower() for e in split(r"(\d+)", components[-1])),
109
+ )
110
+ else:
111
+ return (
112
+ 5,
113
+ len(components),
114
+ tuple(
115
+ int(e) if e.isdigit() else e.lower()
116
+ for comp in components[:-1]
117
+ for e in split(r"(\d+)", comp)
118
+ if e
119
+ ),
120
+ tuple(),
121
+ )
122
+ else:
123
+ return (1, item)
124
+
125
+ elif item is None:
71
126
  return (0,)
72
127
 
73
128
  elif isinstance(item, bool):
74
- return (1, item)
129
+ return (2, item)
75
130
 
76
131
  elif isinstance(item, (int, float)):
77
- return (2, item)
132
+ return (3, item)
78
133
 
79
134
  elif isinstance(item, datetime):
80
- return (3, item.timestamp())
135
+ return (4, item.timestamp())
81
136
 
82
137
  elif isinstance(item, Path):
83
- return (4, item.as_posix())
138
+ return _string_fallback(item.as_posix())
84
139
 
85
- elif isinstance(item, str):
86
- if not item:
87
- return (5, item)
140
+ else:
141
+ try:
142
+ return _string_fallback(f"{item}")
143
+ except Exception:
144
+ return (6, item)
88
145
 
89
- else:
146
+
147
+ def natural_sort_key(item: object) -> tuple[int, ...]:
148
+ """
149
+ A key for natural sorting of various Python types.
150
+
151
+ - Won't sort string version numbers
152
+ - Will convert strings to floats
153
+ - Will sort strings that are file paths
154
+
155
+ 0. None
156
+ 1. Empty strings
157
+ 2. bool
158
+ 3. int, float (inc. strings that are numbers)
159
+ 4. datetime (inc. strings that are dates)
160
+ 5. strings (including string file paths and paths as POSIX strings) & unknown objects with __str__
161
+ 6. unknown objects
162
+ """
163
+ if isinstance(item, str):
164
+ if item:
90
165
  for date_format in date_formats:
91
166
  try:
92
- return (3, datetime.strptime(item, date_format).timestamp())
167
+ return (4, datetime.strptime(item, date_format).timestamp())
93
168
  except ValueError:
94
169
  continue
95
170
 
96
171
  try:
97
- return (2, float(item))
98
- except Exception:
99
- pass
172
+ return (3, float(item))
173
+ except ValueError:
174
+ # the same as _string_fallback
175
+ components = split(r"[/\\]", item)
176
+ if components[-1]:
177
+ return (
178
+ 5,
179
+ len(components),
180
+ tuple(
181
+ int(e) if e.isdigit() else e.lower()
182
+ for comp in components[:-1]
183
+ for e in split(r"(\d+)", comp)
184
+ if e
185
+ ),
186
+ tuple(int(e) if e.isdigit() else e.lower() for e in split(r"(\d+)", components[-1])),
187
+ )
188
+ else:
189
+ return (
190
+ 5,
191
+ len(components),
192
+ tuple(
193
+ int(e) if e.isdigit() else e.lower()
194
+ for comp in components[:-1]
195
+ for e in split(r"(\d+)", comp)
196
+ if e
197
+ ),
198
+ tuple(),
199
+ )
200
+ else:
201
+ return (1, item)
100
202
 
101
- if "/" in item or "\\" in item:
102
- try:
103
- return (4, Path(item).as_posix())
104
- except Exception:
105
- pass
203
+ elif item is None:
204
+ return (0,)
106
205
 
107
- return (6, item.lower(), tuple(int(match.group()) for match in finditer(r"\d+", item)))
206
+ elif isinstance(item, bool):
207
+ return (2, item)
208
+
209
+ elif isinstance(item, (int, float)):
210
+ return (3, item)
211
+
212
+ elif isinstance(item, datetime):
213
+ return (4, item.timestamp())
214
+
215
+ elif isinstance(item, Path):
216
+ return _string_fallback(item.as_posix())
108
217
 
109
- # For unknown types, attempt to convert to string, or place at end
110
218
  else:
111
219
  try:
112
- return (7, f"{item}".lower())
220
+ return _string_fallback(f"{item}")
113
221
  except Exception:
114
- return (8, item)
222
+ return (6, item)
223
+
224
+
225
+ def fast_sort_key(item: object) -> tuple[int, ...]:
226
+ """
227
+ A faster key for natural sorting of various Python types.
228
+
229
+ - Won't sort strings that are dates very well
230
+ - Won't convert strings to floats
231
+ - Won't sort string file paths very well
232
+ - Will do ok with string version numbers
233
+
234
+ 0. None
235
+ 1. Empty strings
236
+ 2. bool
237
+ 3. int, float
238
+ 4. datetime
239
+ 5. strings (including paths as POSIX strings) & unknown objects with __str__
240
+ 6. unknown objects
241
+ """
242
+ if isinstance(item, str):
243
+ if item:
244
+ return (5, tuple(int(e) if e.isdigit() else e.lower() for e in split(r"(\d+)", item)))
245
+ else:
246
+ return (1, item)
247
+
248
+ elif item is None:
249
+ return (0,)
250
+
251
+ elif isinstance(item, bool):
252
+ return (2, item)
253
+
254
+ elif isinstance(item, (int, float)):
255
+ return (3, item)
256
+
257
+ elif isinstance(item, datetime):
258
+ return (4, item.timestamp())
259
+
260
+ elif isinstance(item, Path):
261
+ return _string_fallback(item.as_posix())
262
+
263
+ else:
264
+ try:
265
+ return _string_fallback(f"{item}")
266
+ except Exception:
267
+ return (6, item)
115
268
 
116
269
 
117
270
  def sort_selection(
@@ -264,14 +417,15 @@ def sort_tree_view(
264
417
  _row_index: list[object],
265
418
  tree_rns: dict[str, int],
266
419
  tree: dict[str, object],
267
- key: Callable = natural_sort_key,
420
+ key: Callable | None = None,
268
421
  reverse: bool = False,
269
422
  ) -> tuple[list[object], dict[int, int]]:
270
423
  if not _row_index or not tree_rns or not tree:
271
424
  return [], {}
272
425
 
273
426
  if key is None:
274
- key = natural_sort_key
427
+ # prefer version_sort_key for iid names
428
+ key = version_sort_key
275
429
 
276
430
  # Create the index map and sorted nodes list
277
431
  mapping = {}
@@ -298,38 +452,80 @@ def sort_tree_view(
298
452
  return sorted_nodes, mapping
299
453
 
300
454
 
301
- # def test_natural_sort_key():
302
- # test_items = [
303
- # None,
304
- # False,
305
- # True,
306
- # 5,
307
- # 3.14,
308
- # datetime(2023, 1, 1),
309
- # "abc123",
310
- # "123abc",
311
- # "abc123def",
312
- # "998zzz",
313
- # "10-01-2023",
314
- # "01-10-2023",
315
- # "fi1le_0.txt",
316
- # "file_2.txt",
317
- # "file_10.txt",
318
- # "file_1.txt",
319
- # "path/to/file_2.txt",
320
- # "path/to/file_10.txt",
321
- # "path/to/file_1.txt",
322
- # "/another/path/file_2.log",
323
- # "/another/path/file_10.log",
324
- # "/another/path/file_1.log",
325
- # "C:\\Windows\\System32\\file_2.dll",
326
- # "C:\\Windows\\System32\\file_10.dll",
327
- # "C:\\Windows\\System32\\file_1.dll",
328
- # ]
329
- # print("Sort objects:", [natural_sort_key(e) for e in test_items])
330
- # sorted_items = sorted(test_items, key=natural_sort_key)
331
- # print("\nNatural Sort Order:", sorted_items)
332
-
333
-
334
455
  # if __name__ == "__main__":
335
- # test_natural_sort_key()
456
+ # test_cases = {
457
+ # "Filenames": ["file10.txt", "file2.txt", "file1.txt"],
458
+ # "Versions": ["1.10", "1.2", "1.9", "1.21"],
459
+ # "Mixed": ["z1.doc", "z10.doc", "z2.doc", "z100.doc"],
460
+ # "Paths": [
461
+ # "/folder/file.txt",
462
+ # "/folder/file (1).txt",
463
+ # "/folder (1)/file.txt",
464
+ # "/folder (10)/file.txt",
465
+ # "/dir/subdir/file1.txt",
466
+ # "/dir/subdir/file10.txt",
467
+ # "/dir/subdir/file2.txt",
468
+ # "/dir/file.txt",
469
+ # "/dir/sub/file123.txt",
470
+ # "/dir/sub/file12.txt",
471
+ # # New challenging cases
472
+ # "/a/file.txt",
473
+ # "/a/file1.txt",
474
+ # "/a/b/file.txt",
475
+ # "/a/b/file1.txt",
476
+ # "/x/file-v1.2.txt",
477
+ # "/x/file-v1.10.txt",
478
+ # # My own new challenging cases
479
+ # "/a/zzzzz.txt",
480
+ # "/a/b/a.txt",
481
+ # ],
482
+ # "Case": ["Apple", "banana", "Corn", "apple", "Banana", "corn"],
483
+ # "Leading Zeros": ["001", "010", "009", "100"],
484
+ # "Complex": ["x-1-y-10", "x-1-y-2", "x-2-y-1", "x-10-y-1"],
485
+ # "Lengths": ["2 ft 9 in", "2 ft 10 in", "10 ft 1 in", "10 ft 2 in"],
486
+ # "Floats": [
487
+ # "1.5",
488
+ # "1.25",
489
+ # "1.025",
490
+ # "10.5",
491
+ # "-10.2",
492
+ # "-2.5",
493
+ # "5.7",
494
+ # "-1.25",
495
+ # "0.0",
496
+ # "1.5e3",
497
+ # "2.5e2",
498
+ # "1.23e4",
499
+ # "1e-2",
500
+ # "file1.2.txt",
501
+ # "file1.5.txt",
502
+ # "file1.10.txt",
503
+ # ],
504
+ # "Strings": [
505
+ # "123abc",
506
+ # "abc123",
507
+ # "123abc456",
508
+ # "abc123def",
509
+ # ],
510
+ # }
511
+
512
+ # print("FAST SORT KEY:")
513
+
514
+ # for name, data in test_cases.items():
515
+ # sorted1 = sorted(data, key=fast_sort_key)
516
+ # print(f"\n{name}:")
517
+ # print(sorted1)
518
+
519
+ # print("\nNATURAL SORT KEY:")
520
+
521
+ # for name, data in test_cases.items():
522
+ # sorted1 = sorted(data, key=natural_sort_key)
523
+ # print(f"\n{name}:")
524
+ # print(sorted1)
525
+
526
+ # print("\nVERSION SORT KEY:")
527
+
528
+ # for name, data in test_cases.items():
529
+ # sorted1 = sorted(data, key=version_sort_key)
530
+ # print(f"\n{name}:")
531
+ # print(sorted1)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: tksheet
3
- Version: 7.4.2
3
+ Version: 7.4.4
4
4
  Summary: Tkinter table / sheet and treeview widget
5
5
  Author-email: ragardner <github@ragardner.simplelogin.com>
6
6
  License: Copyright (c) 2019 ragardner and open source contributors
@@ -101,6 +101,8 @@ License-File: LICENSE.txt
101
101
  - [In-built natural sorting](https://github.com/ragardner/tksheet/wiki/Version-7#sorting-the-table)
102
102
  - [Optional built-in find window](https://github.com/ragardner/tksheet/wiki/Version-7#table-functionality-and-bindings)
103
103
 
104
+ Note that due to the limitations of the Tkinter Canvas right-to-left (RTL) languages are not supported.
105
+
104
106
  ```python
105
107
  """
106
108
  Versions 7+ have succinct and easy to read syntax:
@@ -0,0 +1,22 @@
1
+ tksheet/__init__.py,sha256=DJduXwiEFlPhe0uDqKUZCRGgf9pB-aubUvC_sXCpVp8,2313
2
+ tksheet/colors.py,sha256=dHhmdFuQDlwohDHsAfT9VdrKoSl_R33L72a3HCin5zo,51591
3
+ tksheet/column_headers.py,sha256=Dq3GqcUvBpereMdOTU3OqkfSkCb8nSUM18uHF2Vsrqo,103442
4
+ tksheet/constants.py,sha256=PkvAtdYXSOiOO5zuxqrObvGIVV2sEE0enN8YLhI8zCc,3922
5
+ tksheet/find_window.py,sha256=JfkgpGluSng3bKMBneDNQg-AJmBcmCW7JIhtYbSUZaE,8036
6
+ tksheet/formatters.py,sha256=21ZkMaDIJNUtjvtlAbPl8Y19I9nDxue-JJegw6hblz8,10551
7
+ tksheet/functions.py,sha256=KQe1AncAiVUxvcq9GAJ8TBXKaSAxk5-VDHDY6hHApLk,53183
8
+ tksheet/main_table.py,sha256=jWG9Of249O-bPujDG1umoXZUndM6wIsx8lqNWPqVTz4,358190
9
+ tksheet/other_classes.py,sha256=ADybikLipEG4NABXx8bGVAovJJhWcomQOWTorzS1CPU,16581
10
+ tksheet/row_index.py,sha256=NUpv_cpGDKO_tF4tICcK2BaW1Z0-w8sdE9NYVyHNHO8,133849
11
+ tksheet/sheet.py,sha256=s_9jyRok7AGanMZ9NzQYjkFxlwavjEMSBnOEN3uoJoQ,283724
12
+ tksheet/sheet_options.py,sha256=A_cJEc6XirLO8YrWQHfb10lK2VhGbz28_3XgrKx3TfM,9753
13
+ tksheet/sorting.py,sha256=xBSh_b7QyRRDzwqmKMPEevCZY1KpmMbyA6kl0yvcK6U,16887
14
+ tksheet/text_editor.py,sha256=ZLVF-0WxDin5qUAJ5r7dmsdwvhyEoxw0PlPvi_AGNPE,7328
15
+ tksheet/themes.py,sha256=AoNAxibnQi04MN0Zpbn9-kyDnkiiV8TDNWP9FYjpuf0,18473
16
+ tksheet/tksheet_types.py,sha256=8JQVlA6N9jEZTEAytbcyuhOGuNE4fUPxYhTqoidxEE0,588
17
+ tksheet/top_left_rectangle.py,sha256=KhTT-rBUwQTgaHjSwL83cL5_71k2L1B7gxkSxZlTSK8,8598
18
+ tksheet-7.4.4.dist-info/LICENSE.txt,sha256=ndbcCPe9SlHfweE_W2RAueWUe2k7yudyxYLq6WjFdn4,1101
19
+ tksheet-7.4.4.dist-info/METADATA,sha256=F4df5FisLoL6dkzwUbayE2Djt0fgD1bibXlVOFLaoDM,7839
20
+ tksheet-7.4.4.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
21
+ tksheet-7.4.4.dist-info/top_level.txt,sha256=my61PXCcck_HHAc9cq3NAlyAr3A3FXxCy9gptEOaCN8,8
22
+ tksheet-7.4.4.dist-info/RECORD,,
@@ -1,22 +0,0 @@
1
- tksheet/__init__.py,sha256=BuxXbfmHJXeezYFzX9NIu-OAj8cCTE8KGSuIo2uJUXY,2241
2
- tksheet/colors.py,sha256=dHhmdFuQDlwohDHsAfT9VdrKoSl_R33L72a3HCin5zo,51591
3
- tksheet/column_headers.py,sha256=ZfHLhXEKe8M9ggf3nzg252DBVH_NRtQichtTF7pisIc,103245
4
- tksheet/constants.py,sha256=PkvAtdYXSOiOO5zuxqrObvGIVV2sEE0enN8YLhI8zCc,3922
5
- tksheet/find_window.py,sha256=JfkgpGluSng3bKMBneDNQg-AJmBcmCW7JIhtYbSUZaE,8036
6
- tksheet/formatters.py,sha256=21ZkMaDIJNUtjvtlAbPl8Y19I9nDxue-JJegw6hblz8,10551
7
- tksheet/functions.py,sha256=tVwMLWsW4n5M-RCL8DrLMOXygDhBP26wz4nEQq1gkUo,52734
8
- tksheet/main_table.py,sha256=mK74o6VlN8EdM6iJt3XyLlJc4DvNGENyjmDJcNEIcX0,357780
9
- tksheet/other_classes.py,sha256=ADybikLipEG4NABXx8bGVAovJJhWcomQOWTorzS1CPU,16581
10
- tksheet/row_index.py,sha256=66AZdMHiP85cqSYe01sSOVdue-DDEIlpLH2NRSeH5Fw,133222
11
- tksheet/sheet.py,sha256=2_fMNnJqwvEkYsrDuyxFC68l6XZROzZVjG2FrSZc7IU,284689
12
- tksheet/sheet_options.py,sha256=RAQX9NF8Bvauj4fHW-8TGtZG6cRVSzwGxSUKplg3c28,9625
13
- tksheet/sorting.py,sha256=Lh2jrbh6fGXxA7FuosiBFKj7etJe3IYJ33cVhE6g8qU,10374
14
- tksheet/text_editor.py,sha256=ZLVF-0WxDin5qUAJ5r7dmsdwvhyEoxw0PlPvi_AGNPE,7328
15
- tksheet/themes.py,sha256=AoNAxibnQi04MN0Zpbn9-kyDnkiiV8TDNWP9FYjpuf0,18473
16
- tksheet/tksheet_types.py,sha256=8JQVlA6N9jEZTEAytbcyuhOGuNE4fUPxYhTqoidxEE0,588
17
- tksheet/top_left_rectangle.py,sha256=KhTT-rBUwQTgaHjSwL83cL5_71k2L1B7gxkSxZlTSK8,8598
18
- tksheet-7.4.2.dist-info/LICENSE.txt,sha256=ndbcCPe9SlHfweE_W2RAueWUe2k7yudyxYLq6WjFdn4,1101
19
- tksheet-7.4.2.dist-info/METADATA,sha256=QeRPqI62tWbTBzRkMxe-mIhXNHB8xLP44SWDfGglYr0,7734
20
- tksheet-7.4.2.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
21
- tksheet-7.4.2.dist-info/top_level.txt,sha256=my61PXCcck_HHAc9cq3NAlyAr3A3FXxCy9gptEOaCN8,8
22
- tksheet-7.4.2.dist-info/RECORD,,