listpick 0.1.13.61__py3-none-any.whl → 0.1.14.0__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 listpick might be problematic. Click here for more details.
- listpick/listpick_app.py +314 -70
- listpick/ui/footer.py +98 -16
- listpick/utils/generate_data.py +4 -1
- listpick/utils/table_to_list_of_lists.py +51 -36
- listpick/utils/utils.py +7 -4
- {listpick-0.1.13.61.dist-info → listpick-0.1.14.0.dist-info}/METADATA +6 -5
- {listpick-0.1.13.61.dist-info → listpick-0.1.14.0.dist-info}/RECORD +11 -11
- {listpick-0.1.13.61.dist-info → listpick-0.1.14.0.dist-info}/WHEEL +0 -0
- {listpick-0.1.13.61.dist-info → listpick-0.1.14.0.dist-info}/entry_points.txt +0 -0
- {listpick-0.1.13.61.dist-info → listpick-0.1.14.0.dist-info}/licenses/LICENSE.txt +0 -0
- {listpick-0.1.13.61.dist-info → listpick-0.1.14.0.dist-info}/top_level.txt +0 -0
listpick/listpick_app.py
CHANGED
|
@@ -106,6 +106,7 @@ class Picker:
|
|
|
106
106
|
|
|
107
107
|
selections: dict = {},
|
|
108
108
|
cell_selections: dict[tuple[int,int], bool] = {},
|
|
109
|
+
selected_cells_by_row: dict = {},
|
|
109
110
|
highlight_full_row: bool =False,
|
|
110
111
|
cell_cursor: bool = False,
|
|
111
112
|
|
|
@@ -173,6 +174,19 @@ class Picker:
|
|
|
173
174
|
debug: bool = False,
|
|
174
175
|
debug_level: int = 1,
|
|
175
176
|
|
|
177
|
+
command_stack: list = [],
|
|
178
|
+
|
|
179
|
+
loaded_file: str = "Untitled",
|
|
180
|
+
loaded_files: list[str] = ["Untitled"],
|
|
181
|
+
loaded_file_index: int = 0,
|
|
182
|
+
loaded_file_states: list[dict] = [{}],
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
sheets = ["Untitled"],
|
|
186
|
+
sheet_name = "Untitled",
|
|
187
|
+
sheet_index = 0,
|
|
188
|
+
sheet_states = [{}],
|
|
189
|
+
|
|
176
190
|
):
|
|
177
191
|
self.stdscr = stdscr
|
|
178
192
|
self.items = items
|
|
@@ -225,6 +239,7 @@ class Picker:
|
|
|
225
239
|
|
|
226
240
|
self.selections = selections
|
|
227
241
|
self.cell_selections = cell_selections
|
|
242
|
+
self.selected_cells_by_row = selected_cells_by_row
|
|
228
243
|
self.highlight_full_row = highlight_full_row
|
|
229
244
|
self.cell_cursor = cell_cursor
|
|
230
245
|
|
|
@@ -283,7 +298,7 @@ class Picker:
|
|
|
283
298
|
self.registers = {}
|
|
284
299
|
|
|
285
300
|
self.SORT_METHODS = SORT_METHODS
|
|
286
|
-
self.command_stack =
|
|
301
|
+
self.command_stack = command_stack
|
|
287
302
|
self.leftmost_column = leftmost_column
|
|
288
303
|
self.leftmost_char = leftmost_char
|
|
289
304
|
|
|
@@ -297,7 +312,6 @@ class Picker:
|
|
|
297
312
|
self.cursor_pos_prev = 0
|
|
298
313
|
self.ids = []
|
|
299
314
|
self.ids_tuples = []
|
|
300
|
-
self.selected_cells_by_row = {}
|
|
301
315
|
|
|
302
316
|
# History variables
|
|
303
317
|
self.history_filter_and_search = history_filter_and_search
|
|
@@ -312,6 +326,17 @@ class Picker:
|
|
|
312
326
|
self.debug = debug
|
|
313
327
|
self.debug_level = debug_level
|
|
314
328
|
|
|
329
|
+
# Multiple file support
|
|
330
|
+
self.loaded_files = loaded_files
|
|
331
|
+
self.loaded_file = loaded_file
|
|
332
|
+
self.loaded_file_index = loaded_file_index
|
|
333
|
+
self.loaded_file_states = loaded_file_states
|
|
334
|
+
|
|
335
|
+
# Multiple sheet support
|
|
336
|
+
self.sheet_index = sheet_index
|
|
337
|
+
self.sheet_name = sheet_name
|
|
338
|
+
self.sheet_states = sheet_states
|
|
339
|
+
self.sheets = sheets
|
|
315
340
|
|
|
316
341
|
self.initialise_picker_state(reset_colours=self.reset_colours)
|
|
317
342
|
|
|
@@ -320,6 +345,8 @@ class Picker:
|
|
|
320
345
|
self.footer = self.footer_options[self.footer_style]
|
|
321
346
|
|
|
322
347
|
|
|
348
|
+
|
|
349
|
+
|
|
323
350
|
def calculate_section_sizes(self):
|
|
324
351
|
"""
|
|
325
352
|
Calculte the following for the Picker:
|
|
@@ -495,8 +522,12 @@ class Picker:
|
|
|
495
522
|
|
|
496
523
|
if len(self.items) and len(self.cell_selections) != len(self.items)*len(self.items[0]):
|
|
497
524
|
self.cell_selections = {(i, j) : False if (i, j) not in self.cell_selections else self.cell_selections[(i, j)] for i in range(len(self.items)) for j in range(len(self.items[0]))}
|
|
525
|
+
self.selected_cells_by_row = get_selected_cells_by_row(self.cell_selections)
|
|
498
526
|
elif len(self.items) == 0:
|
|
499
527
|
self.cell_selections = {}
|
|
528
|
+
self.selected_cells_by_row = {}
|
|
529
|
+
|
|
530
|
+
|
|
500
531
|
|
|
501
532
|
if len(self.require_option) < len(self.items):
|
|
502
533
|
self.require_option += [self.require_option_default for i in range(len(self.items)-len(self.require_option))]
|
|
@@ -556,6 +587,20 @@ class Picker:
|
|
|
556
587
|
assert new_pos < len(self.items)
|
|
557
588
|
self.cursor_pos = new_pos
|
|
558
589
|
|
|
590
|
+
# Sheets and files
|
|
591
|
+
if len(self.sheet_states) < len(self.sheets):
|
|
592
|
+
self.sheet_states += [{} for _ in range(len(self.sheets) - len(self.sheet_states))]
|
|
593
|
+
if len(self.sheets):
|
|
594
|
+
if self.sheet_index >= len(self.sheets):
|
|
595
|
+
self.sheet_index = 0
|
|
596
|
+
self.sheet_name = self.sheets[self.sheet_index]
|
|
597
|
+
|
|
598
|
+
if len(self.loaded_file_states) < len(self.loaded_files):
|
|
599
|
+
self.loaded_file_states += [{} for _ in range(len(self.loaded_files) - len(self.loaded_file_states))]
|
|
600
|
+
if len(self.loaded_files):
|
|
601
|
+
if self.loaded_file_index >= len(self.loaded_files):
|
|
602
|
+
self.loaded_file_index = 0
|
|
603
|
+
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
559
604
|
|
|
560
605
|
# if tracking and len(self.items) > 1:
|
|
561
606
|
# Ensure that selected indices are tracked upon data refresh
|
|
@@ -591,6 +636,7 @@ class Picker:
|
|
|
591
636
|
self.cursor_pos = [i[0] for i in self.indexed_items].index(cursor_pos_x)
|
|
592
637
|
else:
|
|
593
638
|
self.cursor_pos = 0
|
|
639
|
+
|
|
594
640
|
|
|
595
641
|
|
|
596
642
|
|
|
@@ -1045,6 +1091,7 @@ class Picker:
|
|
|
1045
1091
|
function_data = {
|
|
1046
1092
|
"selections": self.selections,
|
|
1047
1093
|
"cell_selections": self.cell_selections,
|
|
1094
|
+
"selected_cells_by_row": self.selected_cells_by_row,
|
|
1048
1095
|
"items_per_page": self.items_per_page,
|
|
1049
1096
|
"current_row": self.current_row,
|
|
1050
1097
|
"current_page": self.current_page,
|
|
@@ -1136,17 +1183,55 @@ class Picker:
|
|
|
1136
1183
|
"debug_level": self.debug_level,
|
|
1137
1184
|
"reset_colours": self.reset_colours,
|
|
1138
1185
|
"unicode_char_width": self.unicode_char_width,
|
|
1186
|
+
"command_stack": self.command_stack,
|
|
1187
|
+
"loaded_file": self.loaded_file,
|
|
1188
|
+
"loaded_files": self.loaded_files,
|
|
1189
|
+
"loaded_file_index": self.loaded_file_index,
|
|
1190
|
+
"loaded_file_states": self.loaded_file_states,
|
|
1191
|
+
"sheet_index": self.sheet_index,
|
|
1192
|
+
"sheets": self.sheets,
|
|
1193
|
+
"sheet_name": self.sheet_name,
|
|
1194
|
+
"sheet_states": self.sheet_states,
|
|
1139
1195
|
}
|
|
1140
1196
|
return function_data
|
|
1141
1197
|
|
|
1142
|
-
def set_function_data(self, function_data: dict) -> None:
|
|
1198
|
+
def set_function_data(self, function_data: dict, reset_absent_variables: bool = False, do_not_set: list=[]) -> None:
|
|
1143
1199
|
""" Set variables from state dict containing core variables."""
|
|
1144
1200
|
self.logger.info(f"function: set_function_data()")
|
|
1145
1201
|
variables = self.get_function_data().keys()
|
|
1146
1202
|
|
|
1203
|
+
x = Picker(self.stdscr, reset_colours=False)
|
|
1204
|
+
|
|
1205
|
+
|
|
1206
|
+
common_picker_vars = [
|
|
1207
|
+
"loaded_file_index",
|
|
1208
|
+
"loaded_file_states",
|
|
1209
|
+
"loaded_files",
|
|
1210
|
+
"loaded_file",
|
|
1211
|
+
"command_stack",
|
|
1212
|
+
"colour_theme_number",
|
|
1213
|
+
"reset_colours",
|
|
1214
|
+
"show_footer",
|
|
1215
|
+
"show_header",
|
|
1216
|
+
"history_filter_and_search",
|
|
1217
|
+
"history_settings",
|
|
1218
|
+
"history_opts",
|
|
1219
|
+
"history_edits",
|
|
1220
|
+
"history_pipes",
|
|
1221
|
+
"reset_colours",
|
|
1222
|
+
"cell_cursor",
|
|
1223
|
+
"top_gap",
|
|
1224
|
+
"unicode_char_width",
|
|
1225
|
+
"show_row_header",
|
|
1226
|
+
]
|
|
1227
|
+
|
|
1147
1228
|
for var in variables:
|
|
1148
1229
|
if var in function_data:
|
|
1149
1230
|
setattr(self, var, function_data[var])
|
|
1231
|
+
elif reset_absent_variables and var not in common_picker_vars and var not in do_not_set:
|
|
1232
|
+
# Set value to the default for an empty picker
|
|
1233
|
+
setattr(self, var, getattr(x, var))
|
|
1234
|
+
|
|
1150
1235
|
|
|
1151
1236
|
reset_colours = bool("colour_theme_number" in function_data)
|
|
1152
1237
|
self.initialise_picker_state(reset_colours=reset_colours)
|
|
@@ -1260,7 +1345,12 @@ class Picker:
|
|
|
1260
1345
|
message_width = notification_width-5
|
|
1261
1346
|
|
|
1262
1347
|
if not message: message = "!!"
|
|
1263
|
-
|
|
1348
|
+
if type(message) == type(""):
|
|
1349
|
+
mw = message_width
|
|
1350
|
+
submenu_items = [[message[i*mw:(i+1)*mw]] for i in range(len(message)//mw+1)]
|
|
1351
|
+
elif type(message) != type([]):
|
|
1352
|
+
submenu_items = [[" !!"]]
|
|
1353
|
+
|
|
1264
1354
|
|
|
1265
1355
|
notification_remap_keys = {
|
|
1266
1356
|
curses.KEY_RESIZE: curses.KEY_F5,
|
|
@@ -1270,6 +1360,7 @@ class Picker:
|
|
|
1270
1360
|
h, w = stdscr.getmaxyx()
|
|
1271
1361
|
|
|
1272
1362
|
submenu_win = curses.newwin(notification_height, notification_width, 3, w - (notification_width+4))
|
|
1363
|
+
# submenu_win = self.stdscr.subwin(notification_height, notification_width, 3, w - (notification_width+4))
|
|
1273
1364
|
notification_data = {
|
|
1274
1365
|
"items": submenu_items,
|
|
1275
1366
|
"title": title,
|
|
@@ -1286,6 +1377,10 @@ class Picker:
|
|
|
1286
1377
|
"cancel_is_back": True,
|
|
1287
1378
|
"reset_colours": False,
|
|
1288
1379
|
|
|
1380
|
+
"loaded_files": [],
|
|
1381
|
+
"loaded_file_states": [],
|
|
1382
|
+
"loaded_file": "",
|
|
1383
|
+
"loaded_file_index": 0,
|
|
1289
1384
|
}
|
|
1290
1385
|
OptionPicker = Picker(submenu_win, **notification_data)
|
|
1291
1386
|
s, o, f = OptionPicker.run()
|
|
@@ -1338,15 +1433,15 @@ class Picker:
|
|
|
1338
1433
|
# highlights = [highlight for highlight in highlights if "type" not in highlight or highlight["type"] != "search" ]
|
|
1339
1434
|
|
|
1340
1435
|
self.highlights_hide = not self.highlights_hide
|
|
1341
|
-
elif setting[0] == "s":
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1436
|
+
# elif setting[0] == "s":
|
|
1437
|
+
# if 0 <= int(setting[1:]) < len(self.items[0]):
|
|
1438
|
+
# self.sort_column = int(setting[1:])
|
|
1439
|
+
# if len(self.indexed_items):
|
|
1440
|
+
# current_pos = self.indexed_items[self.cursor_pos][0]
|
|
1441
|
+
# sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort items based on new column
|
|
1442
|
+
# if len(self.indexed_items):
|
|
1443
|
+
# new_pos = [row[0] for row in self.indexed_items].index(current_pos)
|
|
1444
|
+
# self.cursor_pos = new_pos
|
|
1350
1445
|
elif setting == "ct":
|
|
1351
1446
|
self.centre_in_terminal = not self.centre_in_terminal
|
|
1352
1447
|
elif setting == "cc":
|
|
@@ -1391,6 +1486,47 @@ class Picker:
|
|
|
1391
1486
|
self.pin_cursor = not self.pin_cursor
|
|
1392
1487
|
elif setting == "unicode":
|
|
1393
1488
|
self.unicode_char_width = not self.unicode_char_width
|
|
1489
|
+
elif setting == "file_next":
|
|
1490
|
+
if len(self.loaded_files) > 1:
|
|
1491
|
+
self.command_stack.append(Command("setting", self.user_settings))
|
|
1492
|
+
# Cache file state
|
|
1493
|
+
self.loaded_file_states[self.loaded_file_index] = self.get_function_data()
|
|
1494
|
+
|
|
1495
|
+
self.loaded_file_index = (self.loaded_file_index + 1) % len(self.loaded_files)
|
|
1496
|
+
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
1497
|
+
|
|
1498
|
+
# If we already have a loaded state for this file
|
|
1499
|
+
if self.loaded_file_states[self.loaded_file_index]:
|
|
1500
|
+
self.set_function_data(self.loaded_file_states[self.loaded_file_index])
|
|
1501
|
+
else:
|
|
1502
|
+
self.set_function_data({}, reset_absent_variables=True)
|
|
1503
|
+
self.load_file(self.loaded_file)
|
|
1504
|
+
|
|
1505
|
+
elif setting == "sheet_next":
|
|
1506
|
+
if not os.path.exists(self.loaded_file):
|
|
1507
|
+
self.notification(self.stdscr, message=f"File {repr(self.loaded_file)} not found.")
|
|
1508
|
+
return None
|
|
1509
|
+
if len(self.sheets) > 1:
|
|
1510
|
+
self.command_stack.append(Command("setting", self.user_settings))
|
|
1511
|
+
|
|
1512
|
+
# Cache sheet state
|
|
1513
|
+
self.sheet_states[self.sheet_index] = self.get_function_data()
|
|
1514
|
+
self.sheet_index = (self.sheet_index + 1) % len(self.sheets)
|
|
1515
|
+
self.sheet_name = self.sheets[self.sheet_index]
|
|
1516
|
+
|
|
1517
|
+
# If we already have a loaded state for this file
|
|
1518
|
+
if self.sheet_states[self.sheet_index]:
|
|
1519
|
+
self.set_function_data(self.sheet_states[self.sheet_index])
|
|
1520
|
+
else:
|
|
1521
|
+
function_data = {
|
|
1522
|
+
"sheet_index": self.sheet_index,
|
|
1523
|
+
"sheet_name": self.sheet_name,
|
|
1524
|
+
"sheet_states":self.sheet_states,
|
|
1525
|
+
"sheets": self.sheets,
|
|
1526
|
+
}
|
|
1527
|
+
self.set_function_data(function_data, reset_absent_variables=True)
|
|
1528
|
+
self.load_sheet(self.loaded_file, sheet_number=self.sheet_index)
|
|
1529
|
+
|
|
1394
1530
|
|
|
1395
1531
|
elif setting.startswith("ft"):
|
|
1396
1532
|
if len(setting) > 2 and setting[2:].isnumeric():
|
|
@@ -1458,9 +1594,9 @@ class Picker:
|
|
|
1458
1594
|
self.user_settings = ""
|
|
1459
1595
|
return None
|
|
1460
1596
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1597
|
+
if self.user_settings:
|
|
1598
|
+
self.command_stack.append(Command("setting", self.user_settings))
|
|
1599
|
+
self.user_settings = ""
|
|
1464
1600
|
|
|
1465
1601
|
def apply_command(self, command: Command):
|
|
1466
1602
|
self.logger.info(f"function: apply_command()")
|
|
@@ -1768,17 +1904,41 @@ class Picker:
|
|
|
1768
1904
|
]
|
|
1769
1905
|
|
|
1770
1906
|
if s:
|
|
1771
|
-
|
|
1772
|
-
|
|
1907
|
+
restrict_curses(self.stdscr)
|
|
1908
|
+
files_to_load = file_picker()
|
|
1909
|
+
unrestrict_curses(self.stdscr)
|
|
1910
|
+
if files_to_load:
|
|
1773
1911
|
index = list(s.keys())[0]
|
|
1912
|
+
file_to_load = files_to_load[0]
|
|
1774
1913
|
return_val = funcs[index](file_to_load)
|
|
1775
|
-
self.set_function_data(return_val)
|
|
1776
1914
|
|
|
1915
|
+
self.loaded_file_states[self.loaded_file_index] = self.get_function_data()
|
|
1916
|
+
|
|
1917
|
+
self.stdscr.clear()
|
|
1918
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
|
1919
|
+
|
|
1920
|
+
tmp = self.stdscr
|
|
1921
|
+
|
|
1922
|
+
self.loaded_files += files_to_load
|
|
1923
|
+
self.loaded_file_states += [{} for _ in files_to_load]
|
|
1924
|
+
self.loaded_file = file_to_load
|
|
1925
|
+
self.loaded_file_index = len(self.loaded_files)-len(files_to_load)
|
|
1926
|
+
|
|
1927
|
+
|
|
1928
|
+
self.stdscr = tmp
|
|
1929
|
+
|
|
1930
|
+
h, w = self.stdscr.getmaxyx()
|
|
1931
|
+
self.notification(self.stdscr, f"{repr(file_to_load)} has been loaded!")
|
|
1932
|
+
|
|
1933
|
+
self.set_function_data({}, reset_absent_variables=True)
|
|
1934
|
+
self.load_file(self.loaded_file)
|
|
1777
1935
|
# items = return_val["items"]
|
|
1778
1936
|
# header = return_val["header"]
|
|
1779
|
-
self.
|
|
1937
|
+
self.stdscr.clear()
|
|
1938
|
+
# self.initialise_variables()
|
|
1780
1939
|
self.draw_screen(self.indexed_items, self.highlights)
|
|
1781
|
-
self.
|
|
1940
|
+
# self.stdscr.refresh()
|
|
1941
|
+
|
|
1782
1942
|
# if return_val:
|
|
1783
1943
|
# notification(stdscr, message=return_val, title="Error")
|
|
1784
1944
|
|
|
@@ -1910,6 +2070,36 @@ class Picker:
|
|
|
1910
2070
|
self.cursor_pos = current_cursor_pos
|
|
1911
2071
|
|
|
1912
2072
|
|
|
2073
|
+
def load_file(self, filename: str) -> None:
|
|
2074
|
+
if not os.path.exists(filename):
|
|
2075
|
+
self.notification(self.stdscr, message = f"File not found: {filename}")
|
|
2076
|
+
return None
|
|
2077
|
+
|
|
2078
|
+
filetype = guess_file_type(filename)
|
|
2079
|
+
items, header, sheets = table_to_list(filename, file_type=filetype)
|
|
2080
|
+
|
|
2081
|
+
if items != None:
|
|
2082
|
+
self.items = items
|
|
2083
|
+
self.header = header if header != None else []
|
|
2084
|
+
self.sheets = sheets
|
|
2085
|
+
|
|
2086
|
+
|
|
2087
|
+
self.initialise_variables()
|
|
2088
|
+
|
|
2089
|
+
def load_sheet(self, filename: str, sheet_number: int = 0):
|
|
2090
|
+
filetype = guess_file_type(filename)
|
|
2091
|
+
items, header, sheets = table_to_list(filename, file_type=filetype, sheet_number=sheet_number)
|
|
2092
|
+
|
|
2093
|
+
if items != None:
|
|
2094
|
+
self.items = items
|
|
2095
|
+
self.header = header if header != None else []
|
|
2096
|
+
self.sheets = sheets
|
|
2097
|
+
|
|
2098
|
+
self.initialise_variables()
|
|
2099
|
+
|
|
2100
|
+
|
|
2101
|
+
|
|
2102
|
+
|
|
1913
2103
|
|
|
1914
2104
|
def run(self) -> Tuple[list[int], str, dict]:
|
|
1915
2105
|
""" Run the picker. """
|
|
@@ -1958,7 +2148,7 @@ class Picker:
|
|
|
1958
2148
|
|
|
1959
2149
|
while True:
|
|
1960
2150
|
key = self.stdscr.getch()
|
|
1961
|
-
if key:
|
|
2151
|
+
if key != -1:
|
|
1962
2152
|
self.logger.info(f"key={key}")
|
|
1963
2153
|
h, w = self.stdscr.getmaxyx()
|
|
1964
2154
|
if key in self.disabled_keys: continue
|
|
@@ -2040,9 +2230,24 @@ class Picker:
|
|
|
2040
2230
|
|
|
2041
2231
|
elif self.check_key("exit", key, self.keys_dict):
|
|
2042
2232
|
self.stdscr.clear()
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2233
|
+
if len(self.loaded_files) <= 1:
|
|
2234
|
+
function_data = self.get_function_data()
|
|
2235
|
+
function_data["last_key"] = key
|
|
2236
|
+
return [], "", function_data
|
|
2237
|
+
else:
|
|
2238
|
+
del self.loaded_files[self.loaded_file_index]
|
|
2239
|
+
del self.loaded_file_states[self.loaded_file_index]
|
|
2240
|
+
self.loaded_file_index = min(self.loaded_file_index, len(self.loaded_files)-1)
|
|
2241
|
+
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
2242
|
+
|
|
2243
|
+
|
|
2244
|
+
# If we already have a loaded state for this file
|
|
2245
|
+
if self.loaded_file_states[self.loaded_file_index]:
|
|
2246
|
+
self.set_function_data(self.loaded_file_states[self.loaded_file_index])
|
|
2247
|
+
else:
|
|
2248
|
+
self.set_function_data({}, reset_absent_variables=True)
|
|
2249
|
+
self.load_file(self.loaded_file)
|
|
2250
|
+
|
|
2046
2251
|
elif self.check_key("full_exit", key, self.keys_dict):
|
|
2047
2252
|
close_curses(self.stdscr)
|
|
2048
2253
|
exit()
|
|
@@ -2607,6 +2812,7 @@ class Picker:
|
|
|
2607
2812
|
# 4. if self.cancel_is_back (e.g., notification) then we exit
|
|
2608
2813
|
# 4. selecting
|
|
2609
2814
|
|
|
2815
|
+
pass
|
|
2610
2816
|
# Cancel visual de/selection
|
|
2611
2817
|
if self.is_selecting or self.is_deselecting:
|
|
2612
2818
|
self.start_selection = -1
|
|
@@ -2684,41 +2890,51 @@ class Picker:
|
|
|
2684
2890
|
|
|
2685
2891
|
elif self.check_key("mode_next", key, self.keys_dict): # tab key
|
|
2686
2892
|
self.logger.info(f"key_function mode_next")
|
|
2687
|
-
|
|
2688
|
-
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2893
|
+
if len(self.modes):
|
|
2894
|
+
prev_mode_index = self.mode_index
|
|
2895
|
+
self.mode_index = (self.mode_index+1)%len(self.modes)
|
|
2896
|
+
mode = self.modes[self.mode_index]
|
|
2897
|
+
for key, val in mode.items():
|
|
2898
|
+
if key == 'filter':
|
|
2899
|
+
if 'filter' in self.modes[prev_mode_index]:
|
|
2900
|
+
self.filter_query = self.filter_query.replace(self.modes[prev_mode_index]['filter'], '')
|
|
2901
|
+
self.filter_query = f"{self.filter_query.strip()} {val.strip()}".strip()
|
|
2902
|
+
prev_index = self.indexed_items[self.cursor_pos][0] if len(self.indexed_items)>0 else 0
|
|
2903
|
+
|
|
2904
|
+
self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
|
|
2905
|
+
if prev_index in [x[0] for x in self.indexed_items]: new_index = [x[0] for x in self.indexed_items].index(prev_index)
|
|
2906
|
+
else: new_index = 0
|
|
2907
|
+
self.cursor_pos = new_index
|
|
2908
|
+
# Re-sort self.items after applying filter
|
|
2909
|
+
sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort self.items based on new column
|
|
2704
2910
|
elif self.check_key("mode_prev", key, self.keys_dict): # shift+tab key
|
|
2705
2911
|
self.logger.info(f"key_function mode_prev")
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2912
|
+
if len(self.modes):
|
|
2913
|
+
prev_mode_index = self.mode_index
|
|
2914
|
+
self.mode_index = (self.mode_index-1)%len(self.modes)
|
|
2915
|
+
mode = self.modes[self.mode_index]
|
|
2916
|
+
for key, val in mode.items():
|
|
2917
|
+
if key == 'filter':
|
|
2918
|
+
if 'filter' in self.modes[prev_mode_index]:
|
|
2919
|
+
self.filter_query = self.filter_query.replace(self.modes[prev_mode_index]['filter'], '')
|
|
2920
|
+
self.filter_query = f"{self.filter_query.strip()} {val.strip()}".strip()
|
|
2921
|
+
prev_index = self.indexed_items[self.cursor_pos][0] if len(self.indexed_items)>0 else 0
|
|
2922
|
+
self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
|
|
2923
|
+
if prev_index in [x[0] for x in self.indexed_items]: new_index = [x[0] for x in self.indexed_items].index(prev_index)
|
|
2924
|
+
else: new_index = 0
|
|
2925
|
+
self.cursor_pos = new_index
|
|
2926
|
+
# Re-sort self.items after applying filter
|
|
2927
|
+
sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort self.items based on new column
|
|
2928
|
+
elif self.check_key("file_next", key, self.keys_dict):
|
|
2929
|
+
if len(self.loaded_files):
|
|
2930
|
+
self.loaded_file_index = (self.loaded_file_index + 1) % len(self.loaded_files)
|
|
2931
|
+
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
2932
|
+
|
|
2933
|
+
elif self.check_key("file_prev", key, self.keys_dict):
|
|
2934
|
+
if len(self.loaded_files):
|
|
2935
|
+
self.loaded_file_index = (self.loaded_file_index - 1) % len(self.loaded_files)
|
|
2936
|
+
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
2937
|
+
|
|
2722
2938
|
elif self.check_key("pipe_input", key, self.keys_dict):
|
|
2723
2939
|
self.logger.info(f"key_function pipe_input")
|
|
2724
2940
|
# usrtxt = "xargs -d '\n' -I{} "
|
|
@@ -2760,26 +2976,46 @@ class Picker:
|
|
|
2760
2976
|
if not selected_indices:
|
|
2761
2977
|
selected_indices = [self.indexed_items[self.cursor_pos][0]]
|
|
2762
2978
|
|
|
2763
|
-
full_values = [format_row_full(self.items[i], self.hidden_columns) for i in selected_indices] # Use format_row_full for full data
|
|
2764
|
-
|
|
2979
|
+
# full_values = [format_row_full(self.items[i], self.hidden_columns) for i in selected_indices] # Use format_row_full for full data
|
|
2980
|
+
if self.cell_cursor:
|
|
2981
|
+
|
|
2982
|
+
full_values = []
|
|
2983
|
+
for row in self.selected_cells_by_row.keys():
|
|
2984
|
+
selected_cell_row_str = ""
|
|
2985
|
+
for cell in self.selected_cells_by_row[row]:
|
|
2986
|
+
if " " in self.items[row][cell]:
|
|
2987
|
+
selected_cell_row_str += repr(self.items[row][cell])
|
|
2988
|
+
else:
|
|
2989
|
+
selected_cell_row_str += self.items[row][cell]
|
|
2990
|
+
selected_cell_row_str += "\t"
|
|
2991
|
+
full_values.append(selected_cell_row_str.strip())
|
|
2992
|
+
|
|
2993
|
+
|
|
2994
|
+
# full_values = ["\t".join([repr(self.items[key][cell]) for cell in self.selected_cells_by_row[key]]) for key in self.selected_cells_by_row.keys()]
|
|
2995
|
+
# full_values = ["\t".join([self.items[key][cell] for cell in self.selected_cells_by_row[key]]) for key in self.selected_cells_by_row.keys()]
|
|
2996
|
+
else:
|
|
2997
|
+
full_values = [self.items[i][self.selected_column] for i in selected_indices]
|
|
2765
2998
|
if full_values:
|
|
2766
|
-
command = usrtxt.split()
|
|
2999
|
+
# command = usrtxt.split()
|
|
3000
|
+
command = usrtxt
|
|
2767
3001
|
# command = ['xargs', '-d' , '"\n"' '-I', '{}', 'mpv', '{}']
|
|
2768
3002
|
# command = ['xargs', '-d' , '"\n"' '-I', '{}', 'mpv', '{}']
|
|
2769
3003
|
# command = "xargs -d '\n' -I{} mpv {}"
|
|
2770
3004
|
|
|
2771
3005
|
try:
|
|
2772
|
-
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
3006
|
+
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True)
|
|
2773
3007
|
|
|
2774
3008
|
if process.stdin != None:
|
|
2775
3009
|
for value in full_values:
|
|
2776
|
-
process.stdin.write((
|
|
3010
|
+
process.stdin.write((value + '\n').encode())
|
|
3011
|
+
# process.stdin.write((value + '\n').encode())
|
|
2777
3012
|
|
|
2778
3013
|
process.stdin.close()
|
|
2779
3014
|
|
|
2780
3015
|
self.notification(self.stdscr, message=f"{len(full_values)} strings piped to {repr(usrtxt)}")
|
|
2781
3016
|
except Exception as e:
|
|
2782
3017
|
self.notification(self.stdscr, message=f"{e}")
|
|
3018
|
+
# self.notification(self.stdscr, message=f"Error: {str(e)}")
|
|
2783
3019
|
|
|
2784
3020
|
|
|
2785
3021
|
elif self.check_key("open", key, self.keys_dict):
|
|
@@ -3011,7 +3247,8 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
|
|
|
3011
3247
|
""" Parse command line arguments. """
|
|
3012
3248
|
parser = argparse.ArgumentParser(description='Convert table to list of lists.')
|
|
3013
3249
|
# parser.add_argument('filename', type=str, help='The file to process')
|
|
3014
|
-
parser.add_argument('-i', dest='file', help='File containing the table to be converted.')
|
|
3250
|
+
# parser.add_argument('-i', dest='file', help='File containing the table to be converted.')
|
|
3251
|
+
parser.add_argument('-i', dest='file', nargs='+', help='File containing the table to be converted.')
|
|
3015
3252
|
parser.add_argument('--load', '-l', dest='load', type=str, help='Load file from Picker dump.')
|
|
3016
3253
|
parser.add_argument('--stdin', dest='stdin', action='store_true', help='Table passed on stdin')
|
|
3017
3254
|
parser.add_argument('--stdin2', action='store_true', help='Table passed on stdin')
|
|
@@ -3033,7 +3270,8 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
|
|
|
3033
3270
|
}
|
|
3034
3271
|
|
|
3035
3272
|
if args.file:
|
|
3036
|
-
input_arg = args.file
|
|
3273
|
+
input_arg = args.file[0]
|
|
3274
|
+
|
|
3037
3275
|
elif args.stdin:
|
|
3038
3276
|
input_arg = '--stdin'
|
|
3039
3277
|
elif args.stdin2:
|
|
@@ -3071,9 +3309,14 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
|
|
|
3071
3309
|
filetype = args.file_type
|
|
3072
3310
|
|
|
3073
3311
|
|
|
3074
|
-
items, header = table_to_list(input_arg, args.delimiter, filetype)
|
|
3312
|
+
items, header, sheets = table_to_list(input_arg, args.delimiter, filetype)
|
|
3075
3313
|
function_data["items"] = items
|
|
3076
3314
|
if header: function_data["header"] = header
|
|
3315
|
+
function_data["sheets"] = sheets
|
|
3316
|
+
if args.file:
|
|
3317
|
+
function_data["loaded_file"] = args.file[0]
|
|
3318
|
+
function_data["loaded_files"] = args.file
|
|
3319
|
+
|
|
3077
3320
|
return args, function_data
|
|
3078
3321
|
|
|
3079
3322
|
def start_curses() -> curses.window:
|
|
@@ -3158,12 +3401,13 @@ def main() -> None:
|
|
|
3158
3401
|
function_data["centre_in_terminal_vertical"] = True
|
|
3159
3402
|
function_data["highlight_full_row"] = True
|
|
3160
3403
|
function_data["pin_cursor"] = True
|
|
3161
|
-
function_data["display_infobox"] = True
|
|
3162
|
-
function_data["infobox_items"] = [["1"], ["2"], ["3"]]
|
|
3163
|
-
function_data["infobox_title"] = "Title"
|
|
3404
|
+
# function_data["display_infobox"] = True
|
|
3405
|
+
# function_data["infobox_items"] = [["1"], ["2"], ["3"]]
|
|
3406
|
+
# function_data["infobox_title"] = "Title"
|
|
3164
3407
|
# function_data["footer_string"] = "Title"
|
|
3165
3408
|
function_data["highlights"] = highlights
|
|
3166
|
-
function_data["show_footer"] = False
|
|
3409
|
+
# function_data["show_footer"] = False
|
|
3410
|
+
# function_data["paginate"] = True
|
|
3167
3411
|
# function_data["debug"] = True
|
|
3168
3412
|
# function_data["debug_level"] = 1
|
|
3169
3413
|
stdscr = start_curses()
|
listpick/ui/footer.py
CHANGED
|
@@ -50,32 +50,109 @@ class StandardFooter(Footer):
|
|
|
50
50
|
def draw(self, h, w):
|
|
51
51
|
state = self.get_state()
|
|
52
52
|
# Fill background
|
|
53
|
-
for i in range(self.height):
|
|
54
|
-
self.stdscr.addstr(h-self.height+i, 0, ' '*(w-1), curses.color_pair(self.colours_start+20))
|
|
55
53
|
|
|
54
|
+
|
|
55
|
+
sheets_y=-1
|
|
56
56
|
if state["footer_string"]:
|
|
57
|
-
footer_string_width = min(w-1, len(state["footer_string"])+2)
|
|
58
57
|
|
|
59
|
-
disp_string = f"{state["footer_string"][:footer_string_width]}"
|
|
60
|
-
disp_string = f" {disp_string:>{footer_string_width-2}} "
|
|
61
|
-
self.stdscr.addstr(h - 1, w-footer_string_width-1, " "*footer_string_width, curses.color_pair(self.colours_start+24))
|
|
62
|
-
self.stdscr.addstr(h - 1, w-footer_string_width-1, f"{disp_string}", curses.color_pair(self.colours_start+24))
|
|
63
58
|
|
|
59
|
+
footer_string_y = h-1
|
|
64
60
|
picker_info_y = h-3
|
|
65
61
|
sort_info_y = h-2
|
|
62
|
+
|
|
66
63
|
self.height = 3
|
|
67
64
|
|
|
68
65
|
else:
|
|
69
66
|
picker_info_y = h-2
|
|
70
67
|
sort_info_y = h-1
|
|
71
|
-
|
|
72
|
-
select_mode = "C"
|
|
73
|
-
if state["is_selecting"]: select_mode = "VS"
|
|
74
|
-
elif state["is_deselecting"]: select_mode = "VDS"
|
|
75
|
-
if state["pin_cursor"]: select_mode = f"{select_mode} "
|
|
76
|
-
self.stdscr.addstr(h - 1, w-35, f"{select_mode:>33} ", curses.color_pair(self.colours_start+20))
|
|
68
|
+
footer_string_y = -1
|
|
77
69
|
self.height = 2
|
|
78
70
|
|
|
71
|
+
if len(state["sheets"]) > 1:
|
|
72
|
+
self.height += 1
|
|
73
|
+
picker_info_y -= 1
|
|
74
|
+
sort_info_y -= 1
|
|
75
|
+
footer_string_y -= 1
|
|
76
|
+
sheets_y = h-1
|
|
77
|
+
|
|
78
|
+
if len(state["loaded_files"]) > 1 and state["loaded_file"] in state["loaded_files"]:
|
|
79
|
+
self.height += 1
|
|
80
|
+
picker_info_y -= 1
|
|
81
|
+
sort_info_y -= 1
|
|
82
|
+
footer_string_y -= 1
|
|
83
|
+
sheets_y -= 1
|
|
84
|
+
|
|
85
|
+
files_y = h-1
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
for i in range(self.height):
|
|
89
|
+
self.stdscr.addstr(h-self.height+i, 0, ' '*(w-1), curses.color_pair(self.colours_start+20))
|
|
90
|
+
|
|
91
|
+
if len(state["loaded_files"]) > 1 and state["loaded_file"] in state["loaded_files"]:
|
|
92
|
+
|
|
93
|
+
sep = "◢ "
|
|
94
|
+
files = [x.split("/")[-1] for x in state["loaded_files"]]
|
|
95
|
+
filename = state["loaded_file"].split("/")[:-1]
|
|
96
|
+
|
|
97
|
+
files_str = sep.join(files)
|
|
98
|
+
files_str = files_str[:w-2]
|
|
99
|
+
|
|
100
|
+
idx = state["loaded_file_index"]
|
|
101
|
+
current_file_x = sum((len(x) for x in files[:idx])) + idx*len(sep)
|
|
102
|
+
current_file_str = state["loaded_file"].split("/")[-1]
|
|
103
|
+
current_file_x_end = current_file_x + len(current_file_str) + 2
|
|
104
|
+
self.stdscr.addstr(files_y, 0, ' '*(w-1), curses.color_pair(self.colours_start+4))
|
|
105
|
+
if current_file_x_end < w:
|
|
106
|
+
|
|
107
|
+
self.stdscr.addstr(files_y, 0, f" {files_str}", curses.color_pair(self.colours_start+4))
|
|
108
|
+
|
|
109
|
+
self.stdscr.addstr(files_y, current_file_x, f" {current_file_str}{sep[0]}", curses.color_pair(self.colours_start+4) | curses.A_REVERSE)
|
|
110
|
+
else:
|
|
111
|
+
files_str = sep.join(files)
|
|
112
|
+
files_str = files_str[current_file_x_end-w:current_file_x_end][:w-2]
|
|
113
|
+
self.stdscr.addstr(files_y, 0, f" {files_str}", curses.color_pair(self.colours_start+4))
|
|
114
|
+
|
|
115
|
+
self.stdscr.addstr(files_y, w - (len(current_file_str)+3), f" {current_file_str}{sep[0]}", curses.color_pair(self.colours_start+4) | curses.A_REVERSE)
|
|
116
|
+
|
|
117
|
+
if len(state["sheets"]) > 1:
|
|
118
|
+
|
|
119
|
+
sep = "◢ "
|
|
120
|
+
sheets = [x.split("/")[-1] for x in state["sheets"]]
|
|
121
|
+
filename = state["sheet_name"].split("/")[:-1]
|
|
122
|
+
|
|
123
|
+
sheets_str = sep.join(sheets)
|
|
124
|
+
sheets_str = sheets_str[:w-2]
|
|
125
|
+
|
|
126
|
+
idx = state["sheet_index"]
|
|
127
|
+
current_sheet_x = sum((len(x) for x in sheets[:idx])) + idx*len(sep)
|
|
128
|
+
current_sheet_str = state["sheet_name"].split("/")[-1]
|
|
129
|
+
current_sheet_x_end = current_sheet_x + len(current_sheet_str) + 2
|
|
130
|
+
self.stdscr.addstr(sheets_y, 0, ' '*(w-1), curses.color_pair(self.colours_start+4))
|
|
131
|
+
if current_sheet_x_end < w:
|
|
132
|
+
|
|
133
|
+
self.stdscr.addstr(sheets_y, 0, f" {sheets_str}", curses.color_pair(self.colours_start+4))
|
|
134
|
+
|
|
135
|
+
self.stdscr.addstr(sheets_y, current_sheet_x, f" {current_sheet_str}{sep[0]}", curses.color_pair(self.colours_start+4) | curses.A_REVERSE)
|
|
136
|
+
else:
|
|
137
|
+
sheets_str = sep.join(sheets)
|
|
138
|
+
sheets_str = sheets_str[current_sheet_x_end-w:current_sheet_x_end][:w-2]
|
|
139
|
+
self.stdscr.addstr(sheets_y, 0, f" {sheets_str}", curses.color_pair(self.colours_start+4))
|
|
140
|
+
|
|
141
|
+
self.stdscr.addstr(sheets_y, w - (len(current_sheet_str)+3), f" {current_sheet_str}{sep[0]}", curses.color_pair(self.colours_start+4) | curses.A_REVERSE)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if state["footer_string"]:
|
|
147
|
+
footer_string_width = min(w-1, len(state["footer_string"])+2)
|
|
148
|
+
|
|
149
|
+
disp_string = f"{state["footer_string"][:footer_string_width]}"
|
|
150
|
+
disp_string = f" {disp_string:>{footer_string_width-2}} "
|
|
151
|
+
self.stdscr.addstr(footer_string_y, w-footer_string_width-1, " "*footer_string_width, curses.color_pair(self.colours_start+24))
|
|
152
|
+
self.stdscr.addstr(footer_string_y, w-footer_string_width-1, f"{disp_string}", curses.color_pair(self.colours_start+24))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
79
156
|
|
|
80
157
|
if state["filter_query"]:
|
|
81
158
|
self.stdscr.addstr(h - 2, 2, f" Filter: {state['filter_query']} "[:w-40], curses.color_pair(self.colours_start+20) | curses.A_BOLD)
|
|
@@ -93,10 +170,14 @@ class StandardFooter(Footer):
|
|
|
93
170
|
# Cursor & selection info
|
|
94
171
|
selected_count = sum(state["selections"].values())
|
|
95
172
|
if state["paginate"]:
|
|
96
|
-
cursor_disp_str = f" [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])}
|
|
173
|
+
# cursor_disp_str = f" [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])} | Page {state['cursor_pos']//state['items_per_page']}/{len(state['indexed_items'])//state['items_per_page']} | {select_mode}"
|
|
174
|
+
cursor_disp_str = f" [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])} | Page {state['cursor_pos']//state['items_per_page']}/{len(state['indexed_items'])//state['items_per_page']} | {select_mode}"
|
|
97
175
|
else:
|
|
98
176
|
cursor_disp_str = f" [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])} | {select_mode}"
|
|
99
|
-
|
|
177
|
+
|
|
178
|
+
# Maximum chars that should be displayed
|
|
179
|
+
max_chars = min(len(cursor_disp_str)+2, w)
|
|
180
|
+
self.stdscr.addstr(picker_info_y, w-max_chars, f"{cursor_disp_str:>{max_chars-2}} ", curses.color_pair(self.colours_start+20))
|
|
100
181
|
|
|
101
182
|
|
|
102
183
|
# Sort info
|
|
@@ -105,7 +186,8 @@ class StandardFooter(Footer):
|
|
|
105
186
|
sort_order_info = "Desc." if state["sort_reverse"] else "Asc."
|
|
106
187
|
sort_order_info = "▼" if state["sort_reverse"][state['sort_column']] else "▲"
|
|
107
188
|
sort_disp_str = f" Sort: ({sort_column_info}, {sort_method_info}, {sort_order_info}) "
|
|
108
|
-
|
|
189
|
+
max_chars = min(len(sort_disp_str)+2, w)
|
|
190
|
+
self.stdscr.addstr(sort_info_y, w-max_chars, f"{sort_disp_str:>{max_chars-1}}", curses.color_pair(self.colours_start+20))
|
|
109
191
|
|
|
110
192
|
self.stdscr.refresh()
|
|
111
193
|
|
listpick/utils/generate_data.py
CHANGED
|
@@ -25,7 +25,10 @@ def generate_columns(funcs: list, files: list) -> list[list[str]]:
|
|
|
25
25
|
for file in files:
|
|
26
26
|
item = []
|
|
27
27
|
for func in funcs:
|
|
28
|
-
|
|
28
|
+
try:
|
|
29
|
+
item.append(func(file))
|
|
30
|
+
except:
|
|
31
|
+
item.append("")
|
|
29
32
|
items.append(item)
|
|
30
33
|
|
|
31
34
|
return items
|
|
@@ -37,10 +37,27 @@ def strip_whitespace(item: Iterable) -> Iterable:
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
def table_to_list(
|
|
40
|
+
def table_to_list(
|
|
41
|
+
|
|
42
|
+
input_arg: str,
|
|
43
|
+
delimiter:str='\t',
|
|
44
|
+
file_type:Optional[str]=None,
|
|
45
|
+
sheet_number:int = 0,
|
|
46
|
+
|
|
47
|
+
) -> Tuple[list[list[str]], list[str], list[str]]:
|
|
41
48
|
"""
|
|
42
49
|
Convert data string to list. The input_arg
|
|
43
50
|
Currently accepts: csv, tsv, json, xlsx, ods
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
input_arg: filename
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
returns:
|
|
57
|
+
items: list[list[str]]
|
|
58
|
+
header: list[str]
|
|
59
|
+
sheets: list[str]
|
|
60
|
+
|
|
44
61
|
"""
|
|
45
62
|
logger.info("function: table_to_list (table_to_list_of_lists.py)")
|
|
46
63
|
table_data = []
|
|
@@ -76,10 +93,10 @@ def table_to_list(input_arg: str, delimiter:str='\t', file_type:Optional[str]=No
|
|
|
76
93
|
table_data = csv_string_to_list(input_data)
|
|
77
94
|
table_data = strip_whitespace(table_data)
|
|
78
95
|
# table_data = parse_csv_like(input_data, ",")
|
|
79
|
-
return table_data, []
|
|
96
|
+
return table_data, [], []
|
|
80
97
|
except Exception as e:
|
|
81
98
|
print(f"Error reading CSV/TSV input: {e}")
|
|
82
|
-
return [], []
|
|
99
|
+
return [], [], []
|
|
83
100
|
|
|
84
101
|
elif file_type == 'tsv':
|
|
85
102
|
try:
|
|
@@ -99,10 +116,10 @@ def table_to_list(input_arg: str, delimiter:str='\t', file_type:Optional[str]=No
|
|
|
99
116
|
|
|
100
117
|
table_data = parse_csv_like(input_data, delimiter)
|
|
101
118
|
table_data = strip_whitespace(table_data)
|
|
102
|
-
return table_data, []
|
|
119
|
+
return table_data, [], []
|
|
103
120
|
except Exception as e:
|
|
104
121
|
print(f"Error reading CSV/TSV input: {e}")
|
|
105
|
-
return [], []
|
|
122
|
+
return [], [], []
|
|
106
123
|
|
|
107
124
|
elif file_type == 'json':
|
|
108
125
|
try:
|
|
@@ -115,55 +132,53 @@ def table_to_list(input_arg: str, delimiter:str='\t', file_type:Optional[str]=No
|
|
|
115
132
|
input_data = read_file_content(input_arg)
|
|
116
133
|
|
|
117
134
|
table_data = json.loads(input_data)
|
|
118
|
-
return table_data, []
|
|
135
|
+
return table_data, [], []
|
|
119
136
|
except json.JSONDecodeError as e:
|
|
120
137
|
print(f"Error decoding JSON input: {e}")
|
|
121
|
-
return [], []
|
|
138
|
+
return [], [], []
|
|
122
139
|
except FileNotFoundError as e:
|
|
123
140
|
print(f"File not found: {e}")
|
|
124
|
-
return [], []
|
|
141
|
+
return [], [], []
|
|
125
142
|
|
|
126
143
|
elif file_type == 'xlsx':
|
|
127
|
-
|
|
144
|
+
import pandas as pd
|
|
145
|
+
ef = pd.ExcelFile(input_arg)
|
|
146
|
+
sheets = ef.sheet_names
|
|
147
|
+
if sheet_number < len(sheets):
|
|
148
|
+
df = pd.read_excel(input_arg, sheet_name=sheet_number)
|
|
149
|
+
else:
|
|
150
|
+
df = pd.read_excel(input_arg)
|
|
151
|
+
table_data = df.values.tolist()
|
|
128
152
|
try:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
elif input_arg == '--stdin2':
|
|
134
|
-
input_count = int(sys.stdin.readline())
|
|
135
|
-
input_data = "\n".join([sys.stdin.readline() for i in range(input_count)])
|
|
136
|
-
with open('temp.xlsx', 'wb') as f:
|
|
137
|
-
f.write(input_data.encode())
|
|
138
|
-
else:
|
|
139
|
-
input_data = read_file_content(input_arg)
|
|
140
|
-
with open('temp.xlsx', 'wb') as f:
|
|
141
|
-
f.write(input_data.encode())
|
|
142
|
-
|
|
143
|
-
wb = load_workbook(filename='temp.xlsx')
|
|
144
|
-
sheet = wb.active
|
|
145
|
-
for row in sheet.iter_rows(values_only=True):
|
|
146
|
-
table_data.append(list(row))
|
|
147
|
-
return table_data, []
|
|
148
|
-
except Exception as e:
|
|
149
|
-
print(f"Error loading Excel file: {e}")
|
|
150
|
-
return [], []
|
|
153
|
+
header = list(df.columns)
|
|
154
|
+
except:
|
|
155
|
+
header = []
|
|
156
|
+
return table_data, header, sheets
|
|
151
157
|
|
|
152
158
|
elif file_type == 'ods':
|
|
153
159
|
try:
|
|
154
160
|
import pandas as pd
|
|
155
|
-
|
|
161
|
+
ef = pd.ExcelFile(input_arg)
|
|
162
|
+
sheets = ef.sheet_names
|
|
163
|
+
if sheet_number < len(sheets):
|
|
164
|
+
df = pd.read_excel(input_arg, engine='odf', sheet_name=sheet_number)
|
|
165
|
+
else:
|
|
166
|
+
df = pd.read_excel(input_arg, engine='odf')
|
|
156
167
|
table_data = df.values.tolist()
|
|
157
|
-
|
|
168
|
+
try:
|
|
169
|
+
header = list(df.columns)
|
|
170
|
+
except:
|
|
171
|
+
header = []
|
|
172
|
+
return table_data, header, sheets
|
|
158
173
|
except Exception as e:
|
|
159
174
|
print(f"Error loading ODS file: {e}")
|
|
160
|
-
return [], []
|
|
175
|
+
return [], [], []
|
|
161
176
|
elif file_type == 'pkl':
|
|
162
177
|
with open(os.path.expandvars(os.path.expanduser(input_arg)), 'rb') as f:
|
|
163
178
|
loaded_data = pickle.load(f)
|
|
164
179
|
items = loaded_data["items"] if "items" in loaded_data else []
|
|
165
180
|
header = loaded_data["header"] if "header" in loaded_data else []
|
|
166
|
-
return items, header
|
|
181
|
+
return items, header, []
|
|
167
182
|
|
|
168
183
|
if input_arg == '--stdin':
|
|
169
184
|
input_data = sys.stdin.read()
|
|
@@ -175,7 +190,7 @@ def table_to_list(input_arg: str, delimiter:str='\t', file_type:Optional[str]=No
|
|
|
175
190
|
|
|
176
191
|
table_data = parse_csv_like(input_data, delimiter)
|
|
177
192
|
|
|
178
|
-
return table_data, []
|
|
193
|
+
return table_data, [], []
|
|
179
194
|
|
|
180
195
|
if __name__ == '__main__':
|
|
181
196
|
parser = argparse.ArgumentParser(description='Convert table to list of lists.')
|
listpick/utils/utils.py
CHANGED
|
@@ -311,7 +311,7 @@ def openFiles(files: list[str]) -> str:
|
|
|
311
311
|
# return result.stderr.read().decode("utf-8").strip()
|
|
312
312
|
return ""
|
|
313
313
|
|
|
314
|
-
def file_picker() -> str:
|
|
314
|
+
def file_picker() -> list[str]:
|
|
315
315
|
""" Run file picker (yazi by default) and return the path of the file picked. If no file is picked an empty string is returned. """
|
|
316
316
|
|
|
317
317
|
logger.info("function: file_picker (utils.py)")
|
|
@@ -320,10 +320,11 @@ def file_picker() -> str:
|
|
|
320
320
|
|
|
321
321
|
lines = tmpfile.readlines()
|
|
322
322
|
if lines:
|
|
323
|
-
|
|
324
|
-
|
|
323
|
+
filenames = [line.decode("utf-8").strip() for line in lines]
|
|
324
|
+
# filename = lines[0].decode("utf-8").strip()
|
|
325
|
+
return filenames
|
|
325
326
|
else:
|
|
326
|
-
return
|
|
327
|
+
return []
|
|
327
328
|
|
|
328
329
|
|
|
329
330
|
def dir_picker() -> str:
|
|
@@ -350,6 +351,8 @@ def pad_lists_to_same_length(list_of_lists: list[list[str]]) -> list[list[str]]:
|
|
|
350
351
|
""" Ensure that all lists in a list of lists are of the same length. Pad any shorter sublists with empty strings. """
|
|
351
352
|
if not list_of_lists or list_of_lists in [[], [[]]]:
|
|
352
353
|
return []
|
|
354
|
+
if type(list_of_lists) == type([]) and len(list_of_lists) and type(list_of_lists[0]) == type(""):
|
|
355
|
+
list_of_lists = [[x] for x in list_of_lists]
|
|
353
356
|
|
|
354
357
|
# Find the maximum length of the sublists
|
|
355
358
|
lengths = [len(sublist) for sublist in list_of_lists]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: listpick
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14.0
|
|
4
4
|
Summary: Listpick is a powerful TUI data tool for creating TUI apps or viewing/comparing tabulated data.
|
|
5
5
|
Home-page: https://github.com/grimandgreedy/listpick
|
|
6
6
|
Author: Grim
|
|
@@ -26,6 +26,7 @@ Requires-Dist: pandas; extra == "full"
|
|
|
26
26
|
Requires-Dist: pyperclip; extra == "full"
|
|
27
27
|
Requires-Dist: toml; extra == "full"
|
|
28
28
|
Requires-Dist: traitlets; extra == "full"
|
|
29
|
+
Requires-Dist: odfpy; extra == "full"
|
|
29
30
|
Dynamic: author
|
|
30
31
|
Dynamic: author-email
|
|
31
32
|
Dynamic: classifier
|
|
@@ -80,7 +81,7 @@ close_curses(stdscr)
|
|
|
80
81
|
Use the listpick binary to generate and display rows based on a list of commands:
|
|
81
82
|
|
|
82
83
|
```
|
|
83
|
-
wget https://raw.githubusercontent.com/grimandgreedy/listpick/refs/heads/master/examples/list_files.toml
|
|
84
|
+
wget https://raw.githubusercontent.com/grimandgreedy/listpick/refs/heads/master/examples/data_generation/list_files.toml
|
|
84
85
|
listpick -g list_files.py
|
|
85
86
|
```
|
|
86
87
|
|
|
@@ -100,9 +101,9 @@ The application allows you to:
|
|
|
100
101
|
## Examples
|
|
101
102
|
|
|
102
103
|
|
|
103
|
-
### Identify video duplicates (./examples/video_duplicates.toml):
|
|
104
|
+
### Identify video duplicates (./examples/data_generation//video_duplicates.toml):
|
|
104
105
|
```python
|
|
105
|
-
listpick -g ./examples/video_duplicates.toml
|
|
106
|
+
listpick -g ./examples/data_generation/video_duplicates.toml
|
|
106
107
|
```
|
|
107
108
|
- From the list of commands in the toml file we generate the properties we will use to identify the duplicates.
|
|
108
109
|
|
|
@@ -142,7 +143,7 @@ listpick -i ~/dn.pkl -t pkl
|
|
|
142
143
|
|
|
143
144
|
2. **Generate data based on an toml file with relevant commands to generate the rows.**
|
|
144
145
|
```python
|
|
145
|
-
listpick -g ./examples/video_duplicates.toml
|
|
146
|
+
listpick -g ./examples/data_generation/video_duplicates.toml
|
|
146
147
|
```
|
|
147
148
|
|
|
148
149
|
- See ./examples/
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
listpick/__init__.py,sha256=ExXc97-bibodH--wlwpQivl0zCNR5D1hvpvrf7OBofU,154
|
|
2
2
|
listpick/__main__.py,sha256=wkCjDdqw093W27yWwnlC3nG_sMRKaIad7hHHWy0RBgY,193
|
|
3
|
-
listpick/listpick_app.py,sha256=
|
|
3
|
+
listpick/listpick_app.py,sha256=3nGXMVky_jftqWbzSLTkW6GAm0_lCKaJGe_WBTAo5xU,176388
|
|
4
4
|
listpick/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
listpick/ui/build_help.py,sha256=_rVKKrX3HfFJtw-pyeNb2lQWbml4-AAw8sZIUYGn97Y,8731
|
|
6
|
-
listpick/ui/footer.py,sha256=
|
|
6
|
+
listpick/ui/footer.py,sha256=85sVZ6zvzZXSc0TwgvCHGuzGElp6KwUVzwzF5VQwLyY,14219
|
|
7
7
|
listpick/ui/help_screen.py,sha256=zbfGIgb-IXtATpl4_Sx7nPbsnRXZ7eiMYlCKGS9EFmw,5608
|
|
8
8
|
listpick/ui/input_field.py,sha256=eyoWHoApdZybjfXcp7Eth7xwb-C-856ZVnq5j_Q3Ojs,30412
|
|
9
9
|
listpick/ui/keys.py,sha256=TzaadgBP_rC7jbp--RFJZDOkHd0EB4K1wToDTiVs6CI,13029
|
|
@@ -14,18 +14,18 @@ listpick/utils/clipboard_operations.py,sha256=ORdNm2kgGbfs51xJSvgJPERgoSmBgT11ax
|
|
|
14
14
|
listpick/utils/config.py,sha256=MEnAZg2Rhfl38XofEIN0uoVAOY7I0ftc79Evk3fOiVw,1654
|
|
15
15
|
listpick/utils/dump.py,sha256=60YVIMNtBoYvWhmzfTJOsNGcetOvcCB3_T7yv-bYTPQ,3838
|
|
16
16
|
listpick/utils/filtering.py,sha256=uS9sW0inmFvq0cIrwRC1BfuP8kjAD5IWWtls4jGB-70,1199
|
|
17
|
-
listpick/utils/generate_data.py,sha256=
|
|
17
|
+
listpick/utils/generate_data.py,sha256=7sv6JRhk0-Gcj4kOlkzx4qPNBJZ-GFWg9vM77GktzpI,3073
|
|
18
18
|
listpick/utils/options_selectors.py,sha256=Vbv4jRkUsSPs7g-EQAv9Q0nhYy6Z4sFsJqMjUIe1oeQ,2814
|
|
19
19
|
listpick/utils/paste_operations.py,sha256=7wDXLPlxUgA3CA99gwsm47juWGO2YQ9EJghW06yo9vI,1242
|
|
20
20
|
listpick/utils/picker_log.py,sha256=SW6GmjxpI7YrSf72fSr4O8Ux0fY_OzaSXUgTFdz6Xo4,805
|
|
21
21
|
listpick/utils/search_and_filter_utils.py,sha256=XxGfkyDVXO9OAKcftPat8IReMTFIuTH-jorxI4o84tg,3239
|
|
22
22
|
listpick/utils/searching.py,sha256=Xk5UIqamNHL2L90z3ACB_Giqdpi9iRKoAJ6pKaqaD7Q,3093
|
|
23
23
|
listpick/utils/sorting.py,sha256=WZZiVlVA3Zkcpwji3U5SNFlQ14zVEk3cZJtQirBkecQ,5329
|
|
24
|
-
listpick/utils/table_to_list_of_lists.py,sha256=
|
|
25
|
-
listpick/utils/utils.py,sha256=
|
|
26
|
-
listpick-0.1.
|
|
27
|
-
listpick-0.1.
|
|
28
|
-
listpick-0.1.
|
|
29
|
-
listpick-0.1.
|
|
30
|
-
listpick-0.1.
|
|
31
|
-
listpick-0.1.
|
|
24
|
+
listpick/utils/table_to_list_of_lists.py,sha256=Ox_4OWtZcFp5XWcItlMqE6_Q27YiJz7M9w23Y9vfzYQ,7604
|
|
25
|
+
listpick/utils/utils.py,sha256=McOl9uT3jh7l4TIWeSd8ZGjK_e7r0YZF0Gl20yI6fl0,13873
|
|
26
|
+
listpick-0.1.14.0.dist-info/licenses/LICENSE.txt,sha256=2mP-MRHJptADDNE9VInMNg1tE-C6Qv93Z4CCQKrpg9w,1061
|
|
27
|
+
listpick-0.1.14.0.dist-info/METADATA,sha256=NDlbmPlgFWGV9V3UmkIyhSYXY71kjhp3GKvUkxzOPRE,8090
|
|
28
|
+
listpick-0.1.14.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
29
|
+
listpick-0.1.14.0.dist-info/entry_points.txt,sha256=-QCf_BKIkUz35Y9nkYpjZWs2Qg0KfRna2PAs5DnF6BE,43
|
|
30
|
+
listpick-0.1.14.0.dist-info/top_level.txt,sha256=5mtsGEz86rz3qQDe0D463gGjAfSp6A3EWg4J4AGYr-Q,9
|
|
31
|
+
listpick-0.1.14.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|