listpick 0.1.13.62__tar.gz → 0.1.14.0__tar.gz

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.

Files changed (46) hide show
  1. {listpick-0.1.13.62 → listpick-0.1.14.0}/CHANGELOG.md +10 -0
  2. {listpick-0.1.13.62/src/listpick.egg-info → listpick-0.1.14.0}/PKG-INFO +2 -1
  3. {listpick-0.1.13.62 → listpick-0.1.14.0}/TODO.md +17 -0
  4. {listpick-0.1.13.62 → listpick-0.1.14.0}/requirements.txt +1 -0
  5. {listpick-0.1.13.62 → listpick-0.1.14.0}/setup.py +2 -1
  6. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/listpick_app.py +280 -61
  7. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/ui/footer.py +98 -16
  8. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/table_to_list_of_lists.py +51 -36
  9. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/utils.py +5 -4
  10. {listpick-0.1.13.62 → listpick-0.1.14.0/src/listpick.egg-info}/PKG-INFO +2 -1
  11. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick.egg-info/requires.txt +1 -0
  12. {listpick-0.1.13.62 → listpick-0.1.14.0}/.gitignore +0 -0
  13. {listpick-0.1.13.62 → listpick-0.1.14.0}/LICENSE.txt +0 -0
  14. {listpick-0.1.13.62 → listpick-0.1.14.0}/README.md +0 -0
  15. {listpick-0.1.13.62 → listpick-0.1.14.0}/assets/aria2tui_screenshot.png +0 -0
  16. {listpick-0.1.13.62 → listpick-0.1.14.0}/assets/file_compare.png +0 -0
  17. {listpick-0.1.13.62 → listpick-0.1.14.0}/assets/lpfman.png +0 -0
  18. {listpick-0.1.13.62 → listpick-0.1.14.0}/listpick.py +0 -0
  19. {listpick-0.1.13.62 → listpick-0.1.14.0}/setup.cfg +0 -0
  20. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/__init__.py +0 -0
  21. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/__main__.py +0 -0
  22. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/ui/__init__.py +0 -0
  23. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/ui/build_help.py +0 -0
  24. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/ui/help_screen.py +0 -0
  25. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/ui/input_field.py +0 -0
  26. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/ui/keys.py +0 -0
  27. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/ui/pane_stuff.py +0 -0
  28. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/ui/picker_colours.py +0 -0
  29. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/__init__.py +0 -0
  30. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/clipboard_operations.py +0 -0
  31. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/config.py +0 -0
  32. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/dump.py +0 -0
  33. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/filtering.py +0 -0
  34. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/generate_data.py +0 -0
  35. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/options_selectors.py +0 -0
  36. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/paste_operations.py +0 -0
  37. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/picker_log.py +0 -0
  38. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/search_and_filter_utils.py +0 -0
  39. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/searching.py +0 -0
  40. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/sorting.py +0 -0
  41. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick.egg-info/SOURCES.txt +0 -0
  42. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick.egg-info/dependency_links.txt +0 -0
  43. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick.egg-info/entry_points.txt +0 -0
  44. {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick.egg-info/top_level.txt +0 -0
  45. {listpick-0.1.13.62 → listpick-0.1.14.0}/tests/kitty_control.sh +0 -0
  46. {listpick-0.1.13.62 → listpick-0.1.14.0}/tests/sorting_dates.csv +0 -0
@@ -10,6 +10,9 @@
10
10
  - Bug fixes:
11
11
  - When data was centred vertically it would take an extra draw_screen loop to determine the proper column widths when the column sizes changed. This has been fixed.
12
12
  - Refreshing sorted data would resort it on the selected column rather than the sort column. Fixed.
13
+ - Fixed error when padding uneven lists when a list of strings is passed instead of a list of lists.
14
+ - Fixed crash when showing certain notifications. The notification message was not being split into lines properly.
15
+ - Fixed wrong page number in the footer when paginate=True.
13
16
  - Added some extra options to the settings:
14
17
  - Toggle header
15
18
  - Toggle row header
@@ -31,6 +34,13 @@
31
34
  - Much faster with very large data sets as we need to determine selected_cells_by_row every time we run self.draw_screen()
32
35
  - We can now pipe data from cells in multiple columns to a command.
33
36
  - e.g., pipe two cols to gnuplot
37
+ - Features added:
38
+ - Listpick now supports multiple open files.
39
+ - 'file_next' setting
40
+ - Listpick now supports files with multiple sheets.
41
+ - 'sheet_next' setting
42
+ - Fixed error when opening xlsx files.
43
+ - Can now open multiple files from the command line: listpick -i file1.csv, file2.csv
34
44
 
35
45
  ## [0.1.13] 2025-07-28
36
46
  - Cell-based picker is now supported.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: listpick
3
- Version: 0.1.13.62
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
@@ -5,6 +5,21 @@ ASAP
5
5
  > - [ ] Unify in-app load and command-line input file
6
6
  > - [ ] Implement default_option_selector and pass the picker options (!!!)
7
7
  > - [ ] Make sure that all class initialisation variables are returned in the get_function_variables function.
8
+ > - [ ] Go through each of the class variables and see which should be held as common in set_function_data
9
+ > - [ ] Make input field look better:
10
+ > - [ ] During input
11
+ > - [ ] While being displayed.
12
+ > - [ ] Handle multiple files
13
+ > - [x] Display 'open' files in footer
14
+ > - [x] Open multiple files with file_picker
15
+ > - [x] Switch between 'open' files
16
+ > - [ ] Handle unsaved files.
17
+ > - [x] Remember state when switching between files.
18
+ > - [x] Selections
19
+ > - [ ] Add support in the alternate footers
20
+ > - [x] Handle files with multiple sheets.
21
+ > - [ ] Add support in the alternate footers
22
+ > - [ ] Support opening multiple files from the command line.
8
23
 
9
24
 
10
25
 
@@ -259,6 +274,8 @@ ASAP
259
274
  > - [ ] Add a broader editor_picker option which enables/disables add/remove columns in the settings.
260
275
  > - [ ] If no -t type argument is given then guess filetype.
261
276
  > - [ ] Add row selection using 'V'--select all cells in a row.
277
+ > - [ ] Add option for those who don't use a nerdfont.
278
+ > - [ ] refreshing symbol, pin cursor symbol,
262
279
 
263
280
  > [!Bug] Bugs
264
281
  > - [ ] fix resizing when input field active
@@ -10,3 +10,4 @@ tmp==0.0.2
10
10
  toml==0.10.2
11
11
  traitlets==5.14.3
12
12
  wcwidth==0.2.13
13
+ odfpy
@@ -16,7 +16,7 @@ with open("README.md", "r", encoding = "utf-8") as fh:
16
16
 
17
17
  setuptools.setup(
18
18
  name = "listpick",
19
- version = "0.1.13.62",
19
+ version = "0.1.14.0",
20
20
  author = "Grim",
21
21
  author_email = "grimandgreedy@protonmail.com",
22
22
  description = "Listpick is a powerful TUI data tool for creating TUI apps or viewing/comparing tabulated data.",
@@ -59,6 +59,7 @@ setuptools.setup(
59
59
  "pyperclip",
60
60
  "toml",
61
61
  "traitlets",
62
+ "odfpy",
62
63
  ]
63
64
  },
64
65
 
@@ -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)
@@ -1275,6 +1360,7 @@ class Picker:
1275
1360
  h, w = stdscr.getmaxyx()
1276
1361
 
1277
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))
1278
1364
  notification_data = {
1279
1365
  "items": submenu_items,
1280
1366
  "title": title,
@@ -1291,6 +1377,10 @@ class Picker:
1291
1377
  "cancel_is_back": True,
1292
1378
  "reset_colours": False,
1293
1379
 
1380
+ "loaded_files": [],
1381
+ "loaded_file_states": [],
1382
+ "loaded_file": "",
1383
+ "loaded_file_index": 0,
1294
1384
  }
1295
1385
  OptionPicker = Picker(submenu_win, **notification_data)
1296
1386
  s, o, f = OptionPicker.run()
@@ -1343,15 +1433,15 @@ class Picker:
1343
1433
  # highlights = [highlight for highlight in highlights if "type" not in highlight or highlight["type"] != "search" ]
1344
1434
 
1345
1435
  self.highlights_hide = not self.highlights_hide
1346
- elif setting[0] == "s":
1347
- if 0 <= int(setting[1:]) < len(self.items[0]):
1348
- self.sort_column = int(setting[1:])
1349
- if len(self.indexed_items):
1350
- current_pos = self.indexed_items[self.cursor_pos][0]
1351
- 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
1352
- if len(self.indexed_items):
1353
- new_pos = [row[0] for row in self.indexed_items].index(current_pos)
1354
- self.cursor_pos = new_pos
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
1355
1445
  elif setting == "ct":
1356
1446
  self.centre_in_terminal = not self.centre_in_terminal
1357
1447
  elif setting == "cc":
@@ -1396,6 +1486,47 @@ class Picker:
1396
1486
  self.pin_cursor = not self.pin_cursor
1397
1487
  elif setting == "unicode":
1398
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
+
1399
1530
 
1400
1531
  elif setting.startswith("ft"):
1401
1532
  if len(setting) > 2 and setting[2:].isnumeric():
@@ -1463,9 +1594,9 @@ class Picker:
1463
1594
  self.user_settings = ""
1464
1595
  return None
1465
1596
 
1466
-
1467
- self.command_stack.append(Command("setting", self.user_settings))
1468
- self.user_settings = ""
1597
+ if self.user_settings:
1598
+ self.command_stack.append(Command("setting", self.user_settings))
1599
+ self.user_settings = ""
1469
1600
 
1470
1601
  def apply_command(self, command: Command):
1471
1602
  self.logger.info(f"function: apply_command()")
@@ -1773,17 +1904,41 @@ class Picker:
1773
1904
  ]
1774
1905
 
1775
1906
  if s:
1776
- file_to_load = file_picker()
1777
- if file_to_load:
1907
+ restrict_curses(self.stdscr)
1908
+ files_to_load = file_picker()
1909
+ unrestrict_curses(self.stdscr)
1910
+ if files_to_load:
1778
1911
  index = list(s.keys())[0]
1912
+ file_to_load = files_to_load[0]
1779
1913
  return_val = funcs[index](file_to_load)
1780
- self.set_function_data(return_val)
1781
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)
1782
1935
  # items = return_val["items"]
1783
1936
  # header = return_val["header"]
1784
- self.initialise_variables()
1937
+ self.stdscr.clear()
1938
+ # self.initialise_variables()
1785
1939
  self.draw_screen(self.indexed_items, self.highlights)
1786
- self.notification(self.stdscr, f"{repr(file_to_load)} has been loaded!")
1940
+ # self.stdscr.refresh()
1941
+
1787
1942
  # if return_val:
1788
1943
  # notification(stdscr, message=return_val, title="Error")
1789
1944
 
@@ -1915,6 +2070,36 @@ class Picker:
1915
2070
  self.cursor_pos = current_cursor_pos
1916
2071
 
1917
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
+
1918
2103
 
1919
2104
  def run(self) -> Tuple[list[int], str, dict]:
1920
2105
  """ Run the picker. """
@@ -1963,7 +2148,7 @@ class Picker:
1963
2148
 
1964
2149
  while True:
1965
2150
  key = self.stdscr.getch()
1966
- if key:
2151
+ if key != -1:
1967
2152
  self.logger.info(f"key={key}")
1968
2153
  h, w = self.stdscr.getmaxyx()
1969
2154
  if key in self.disabled_keys: continue
@@ -2045,9 +2230,24 @@ class Picker:
2045
2230
 
2046
2231
  elif self.check_key("exit", key, self.keys_dict):
2047
2232
  self.stdscr.clear()
2048
- function_data = self.get_function_data()
2049
- function_data["last_key"] = key
2050
- return [], "", function_data
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
+
2051
2251
  elif self.check_key("full_exit", key, self.keys_dict):
2052
2252
  close_curses(self.stdscr)
2053
2253
  exit()
@@ -2612,6 +2812,7 @@ class Picker:
2612
2812
  # 4. if self.cancel_is_back (e.g., notification) then we exit
2613
2813
  # 4. selecting
2614
2814
 
2815
+ pass
2615
2816
  # Cancel visual de/selection
2616
2817
  if self.is_selecting or self.is_deselecting:
2617
2818
  self.start_selection = -1
@@ -2689,41 +2890,51 @@ class Picker:
2689
2890
 
2690
2891
  elif self.check_key("mode_next", key, self.keys_dict): # tab key
2691
2892
  self.logger.info(f"key_function mode_next")
2692
- # apply setting
2693
- prev_mode_index = self.mode_index
2694
- self.mode_index = (self.mode_index+1)%len(self.modes)
2695
- mode = self.modes[self.mode_index]
2696
- for key, val in mode.items():
2697
- if key == 'filter':
2698
- if 'filter' in self.modes[prev_mode_index]:
2699
- self.filter_query = self.filter_query.replace(self.modes[prev_mode_index]['filter'], '')
2700
- self.filter_query = f"{self.filter_query.strip()} {val.strip()}".strip()
2701
- prev_index = self.indexed_items[self.cursor_pos][0] if len(self.indexed_items)>0 else 0
2702
-
2703
- self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
2704
- 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)
2705
- else: new_index = 0
2706
- self.cursor_pos = new_index
2707
- # Re-sort self.items after applying filter
2708
- 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
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
2709
2910
  elif self.check_key("mode_prev", key, self.keys_dict): # shift+tab key
2710
2911
  self.logger.info(f"key_function mode_prev")
2711
- # apply setting
2712
- prev_mode_index = self.mode_index
2713
- self.mode_index = (self.mode_index-1)%len(self.modes)
2714
- mode = self.modes[self.mode_index]
2715
- for key, val in mode.items():
2716
- if key == 'filter':
2717
- if 'filter' in self.modes[prev_mode_index]:
2718
- self.filter_query = self.filter_query.replace(self.modes[prev_mode_index]['filter'], '')
2719
- self.filter_query = f"{self.filter_query.strip()} {val.strip()}".strip()
2720
- prev_index = self.indexed_items[self.cursor_pos][0] if len(self.indexed_items)>0 else 0
2721
- self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
2722
- 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)
2723
- else: new_index = 0
2724
- self.cursor_pos = new_index
2725
- # Re-sort self.items after applying filter
2726
- 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
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
+
2727
2938
  elif self.check_key("pipe_input", key, self.keys_dict):
2728
2939
  self.logger.info(f"key_function pipe_input")
2729
2940
  # usrtxt = "xargs -d '\n' -I{} "
@@ -3036,7 +3247,8 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
3036
3247
  """ Parse command line arguments. """
3037
3248
  parser = argparse.ArgumentParser(description='Convert table to list of lists.')
3038
3249
  # parser.add_argument('filename', type=str, help='The file to process')
3039
- 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.')
3040
3252
  parser.add_argument('--load', '-l', dest='load', type=str, help='Load file from Picker dump.')
3041
3253
  parser.add_argument('--stdin', dest='stdin', action='store_true', help='Table passed on stdin')
3042
3254
  parser.add_argument('--stdin2', action='store_true', help='Table passed on stdin')
@@ -3058,7 +3270,8 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
3058
3270
  }
3059
3271
 
3060
3272
  if args.file:
3061
- input_arg = args.file
3273
+ input_arg = args.file[0]
3274
+
3062
3275
  elif args.stdin:
3063
3276
  input_arg = '--stdin'
3064
3277
  elif args.stdin2:
@@ -3096,9 +3309,14 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
3096
3309
  filetype = args.file_type
3097
3310
 
3098
3311
 
3099
- items, header = table_to_list(input_arg, args.delimiter, filetype)
3312
+ items, header, sheets = table_to_list(input_arg, args.delimiter, filetype)
3100
3313
  function_data["items"] = items
3101
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
+
3102
3320
  return args, function_data
3103
3321
 
3104
3322
  def start_curses() -> curses.window:
@@ -3188,7 +3406,8 @@ def main() -> None:
3188
3406
  # function_data["infobox_title"] = "Title"
3189
3407
  # function_data["footer_string"] = "Title"
3190
3408
  function_data["highlights"] = highlights
3191
- function_data["show_footer"] = False
3409
+ # function_data["show_footer"] = False
3410
+ # function_data["paginate"] = True
3192
3411
  # function_data["debug"] = True
3193
3412
  # function_data["debug_level"] = 1
3194
3413
  stdscr = start_curses()
@@ -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'])} Page {state['cursor_pos']//state['items_per_page']}/{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
- self.stdscr.addstr(picker_info_y, w-35, f"{cursor_disp_str:>33} ", curses.color_pair(self.colours_start+20))
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
- self.stdscr.addstr(sort_info_y, w-35, f"{sort_disp_str:>34}", curses.color_pair(self.colours_start+20))
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
 
@@ -37,10 +37,27 @@ def strip_whitespace(item: Iterable) -> Iterable:
37
37
 
38
38
 
39
39
 
40
- def table_to_list(input_arg: str, delimiter:str='\t', file_type:Optional[str]=None) -> Tuple[list[list[str]], list[str]]:
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
- from openpyxl import load_workbook
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
- if input_arg == '--stdin':
130
- input_data = sys.stdin.read()
131
- with open('temp.xlsx', 'wb') as f:
132
- f.write(input_data.encode())
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
- df = pd.read_excel(input_arg, engine='odf')
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
- return table_data, []
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.')
@@ -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
- filename = lines[0].decode("utf-8").strip()
324
- return filename
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:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: listpick
3
- Version: 0.1.13.62
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
@@ -13,3 +13,4 @@ pandas
13
13
  pyperclip
14
14
  toml
15
15
  traitlets
16
+ odfpy
File without changes
File without changes
File without changes
File without changes
File without changes