listpick 0.1.13.62__py3-none-any.whl → 0.1.14.1__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 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
 
@@ -122,7 +123,7 @@ class Picker:
122
123
 
123
124
  paginate: bool =False,
124
125
  cancel_is_back: bool = False,
125
- mode_index: int =0,
126
+ mode_index: int = 0,
126
127
  modes: list[dict] = [],
127
128
  display_modes: bool =False,
128
129
  require_option: list=[],
@@ -161,10 +162,8 @@ class Picker:
161
162
 
162
163
  startup_notification:str = "",
163
164
 
164
- leftmost_column: int = 0,
165
165
  leftmost_char: int = 0,
166
166
 
167
-
168
167
  history_filter_and_search: list[str] = [],
169
168
  history_opts: list[str] = [],
170
169
  history_settings: list[str] = [],
@@ -173,6 +172,19 @@ class Picker:
173
172
  debug: bool = False,
174
173
  debug_level: int = 1,
175
174
 
175
+ command_stack: list = [],
176
+
177
+ loaded_file: str = "Untitled",
178
+ loaded_files: list[str] = ["Untitled"],
179
+ loaded_file_index: int = 0,
180
+ loaded_file_states: list[dict] = [{}],
181
+
182
+
183
+ sheets = ["Untitled"],
184
+ sheet_name = "Untitled",
185
+ sheet_index = 0,
186
+ sheet_states = [{}],
187
+
176
188
  ):
177
189
  self.stdscr = stdscr
178
190
  self.items = items
@@ -225,6 +237,7 @@ class Picker:
225
237
 
226
238
  self.selections = selections
227
239
  self.cell_selections = cell_selections
240
+ self.selected_cells_by_row = selected_cells_by_row
228
241
  self.highlight_full_row = highlight_full_row
229
242
  self.cell_cursor = cell_cursor
230
243
 
@@ -283,8 +296,7 @@ class Picker:
283
296
  self.registers = {}
284
297
 
285
298
  self.SORT_METHODS = SORT_METHODS
286
- self.command_stack = []
287
- self.leftmost_column = leftmost_column
299
+ self.command_stack = command_stack
288
300
  self.leftmost_char = leftmost_char
289
301
 
290
302
 
@@ -297,7 +309,6 @@ class Picker:
297
309
  self.cursor_pos_prev = 0
298
310
  self.ids = []
299
311
  self.ids_tuples = []
300
- self.selected_cells_by_row = {}
301
312
 
302
313
  # History variables
303
314
  self.history_filter_and_search = history_filter_and_search
@@ -307,18 +318,37 @@ class Picker:
307
318
  self.history_edits = history_edits
308
319
 
309
320
 
310
-
311
-
312
321
  self.debug = debug
313
322
  self.debug_level = debug_level
314
323
 
324
+ # Multiple file support
325
+ self.loaded_files = loaded_files
326
+ self.loaded_file = loaded_file
327
+ self.loaded_file_index = loaded_file_index
328
+ self.loaded_file_states = loaded_file_states
329
+
330
+ # Multiple sheet support
331
+ self.sheet_index = sheet_index
332
+ self.sheet_name = sheet_name
333
+ self.sheet_states = sheet_states
334
+ self.sheets = sheets
315
335
 
316
336
  self.initialise_picker_state(reset_colours=self.reset_colours)
317
337
 
318
338
  # Note: We have to set the footer after initialising the picker state so that the footer can use the get_function_data method
319
339
  self.footer_options = [StandardFooter(self.stdscr, colours_start, self.get_function_data), CompactFooter(self.stdscr, colours_start, self.get_function_data), NoFooter(self.stdscr, colours_start, self.get_function_data)]
320
340
  self.footer = self.footer_options[self.footer_style]
341
+ self.__version__ = "1.0"
321
342
 
343
+ def __sizeof__(self):
344
+
345
+ size = super().__sizeof__()
346
+
347
+ # Add the size of each attribute directly owned by the object
348
+ for attr_name in dir(self):
349
+ if not attr_name.startswith('__') and not callable(getattr(self, attr_name)):
350
+ size += sys.getsizeof(getattr(self, attr_name))
351
+ return size
322
352
 
323
353
  def calculate_section_sizes(self):
324
354
  """
@@ -495,8 +525,12 @@ class Picker:
495
525
 
496
526
  if len(self.items) and len(self.cell_selections) != len(self.items)*len(self.items[0]):
497
527
  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]))}
528
+ self.selected_cells_by_row = get_selected_cells_by_row(self.cell_selections)
498
529
  elif len(self.items) == 0:
499
530
  self.cell_selections = {}
531
+ self.selected_cells_by_row = {}
532
+
533
+
500
534
 
501
535
  if len(self.require_option) < len(self.items):
502
536
  self.require_option += [self.require_option_default for i in range(len(self.items)-len(self.require_option))]
@@ -556,6 +590,20 @@ class Picker:
556
590
  assert new_pos < len(self.items)
557
591
  self.cursor_pos = new_pos
558
592
 
593
+ # Sheets and files
594
+ if len(self.sheet_states) < len(self.sheets):
595
+ self.sheet_states += [{} for _ in range(len(self.sheets) - len(self.sheet_states))]
596
+ if len(self.sheets):
597
+ if self.sheet_index >= len(self.sheets):
598
+ self.sheet_index = 0
599
+ self.sheet_name = self.sheets[self.sheet_index]
600
+
601
+ if len(self.loaded_file_states) < len(self.loaded_files):
602
+ self.loaded_file_states += [{} for _ in range(len(self.loaded_files) - len(self.loaded_file_states))]
603
+ if len(self.loaded_files):
604
+ if self.loaded_file_index >= len(self.loaded_files):
605
+ self.loaded_file_index = 0
606
+ self.loaded_file = self.loaded_files[self.loaded_file_index]
559
607
 
560
608
  # if tracking and len(self.items) > 1:
561
609
  # Ensure that selected indices are tracked upon data refresh
@@ -591,6 +639,7 @@ class Picker:
591
639
  self.cursor_pos = [i[0] for i in self.indexed_items].index(cursor_pos_x)
592
640
  else:
593
641
  self.cursor_pos = 0
642
+
594
643
 
595
644
 
596
645
 
@@ -1045,6 +1094,7 @@ class Picker:
1045
1094
  function_data = {
1046
1095
  "selections": self.selections,
1047
1096
  "cell_selections": self.cell_selections,
1097
+ "selected_cells_by_row": self.selected_cells_by_row,
1048
1098
  "items_per_page": self.items_per_page,
1049
1099
  "current_row": self.current_row,
1050
1100
  "current_page": self.current_page,
@@ -1123,7 +1173,6 @@ class Picker:
1123
1173
  "keys_dict": self.keys_dict,
1124
1174
  "cancel_is_back": self.cancel_is_back,
1125
1175
  "paginate": self.paginate,
1126
- "leftmost_column": self.leftmost_column,
1127
1176
  "leftmost_char": self.leftmost_char,
1128
1177
  "history_filter_and_search" : self.history_filter_and_search,
1129
1178
  "history_pipes" : self.history_pipes,
@@ -1136,17 +1185,55 @@ class Picker:
1136
1185
  "debug_level": self.debug_level,
1137
1186
  "reset_colours": self.reset_colours,
1138
1187
  "unicode_char_width": self.unicode_char_width,
1188
+ "command_stack": self.command_stack,
1189
+ "loaded_file": self.loaded_file,
1190
+ "loaded_files": self.loaded_files,
1191
+ "loaded_file_index": self.loaded_file_index,
1192
+ "loaded_file_states": self.loaded_file_states,
1193
+ "sheet_index": self.sheet_index,
1194
+ "sheets": self.sheets,
1195
+ "sheet_name": self.sheet_name,
1196
+ "sheet_states": self.sheet_states,
1139
1197
  }
1140
1198
  return function_data
1141
1199
 
1142
- def set_function_data(self, function_data: dict) -> None:
1200
+ def set_function_data(self, function_data: dict, reset_absent_variables: bool = False, do_not_set: list=[]) -> None:
1143
1201
  """ Set variables from state dict containing core variables."""
1144
1202
  self.logger.info(f"function: set_function_data()")
1145
1203
  variables = self.get_function_data().keys()
1146
1204
 
1205
+ x = Picker(self.stdscr, reset_colours=False)
1206
+
1207
+
1208
+ common_picker_vars = [
1209
+ "loaded_file_index",
1210
+ "loaded_file_states",
1211
+ "loaded_files",
1212
+ "loaded_file",
1213
+ "command_stack",
1214
+ "colour_theme_number",
1215
+ "reset_colours",
1216
+ "show_footer",
1217
+ "show_header",
1218
+ "history_filter_and_search",
1219
+ "history_settings",
1220
+ "history_opts",
1221
+ "history_edits",
1222
+ "history_pipes",
1223
+ "reset_colours",
1224
+ "cell_cursor",
1225
+ "top_gap",
1226
+ "unicode_char_width",
1227
+ "show_row_header",
1228
+ ]
1229
+
1147
1230
  for var in variables:
1148
1231
  if var in function_data:
1149
1232
  setattr(self, var, function_data[var])
1233
+ elif reset_absent_variables and var not in common_picker_vars and var not in do_not_set:
1234
+ # Set value to the default for an empty picker
1235
+ setattr(self, var, getattr(x, var))
1236
+
1150
1237
 
1151
1238
  reset_colours = bool("colour_theme_number" in function_data)
1152
1239
  self.initialise_picker_state(reset_colours=reset_colours)
@@ -1275,6 +1362,7 @@ class Picker:
1275
1362
  h, w = stdscr.getmaxyx()
1276
1363
 
1277
1364
  submenu_win = curses.newwin(notification_height, notification_width, 3, w - (notification_width+4))
1365
+ # submenu_win = self.stdscr.subwin(notification_height, notification_width, 3, w - (notification_width+4))
1278
1366
  notification_data = {
1279
1367
  "items": submenu_items,
1280
1368
  "title": title,
@@ -1291,6 +1379,10 @@ class Picker:
1291
1379
  "cancel_is_back": True,
1292
1380
  "reset_colours": False,
1293
1381
 
1382
+ "loaded_files": [],
1383
+ "loaded_file_states": [],
1384
+ "loaded_file": "",
1385
+ "loaded_file_index": 0,
1294
1386
  }
1295
1387
  OptionPicker = Picker(submenu_win, **notification_data)
1296
1388
  s, o, f = OptionPicker.run()
@@ -1343,15 +1435,15 @@ class Picker:
1343
1435
  # highlights = [highlight for highlight in highlights if "type" not in highlight or highlight["type"] != "search" ]
1344
1436
 
1345
1437
  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
1438
+ # elif setting[0] == "s":
1439
+ # if 0 <= int(setting[1:]) < len(self.items[0]):
1440
+ # self.sort_column = int(setting[1:])
1441
+ # if len(self.indexed_items):
1442
+ # current_pos = self.indexed_items[self.cursor_pos][0]
1443
+ # 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
1444
+ # if len(self.indexed_items):
1445
+ # new_pos = [row[0] for row in self.indexed_items].index(current_pos)
1446
+ # self.cursor_pos = new_pos
1355
1447
  elif setting == "ct":
1356
1448
  self.centre_in_terminal = not self.centre_in_terminal
1357
1449
  elif setting == "cc":
@@ -1396,6 +1488,47 @@ class Picker:
1396
1488
  self.pin_cursor = not self.pin_cursor
1397
1489
  elif setting == "unicode":
1398
1490
  self.unicode_char_width = not self.unicode_char_width
1491
+ elif setting == "file_next":
1492
+ if len(self.loaded_files) > 1:
1493
+ self.command_stack.append(Command("setting", self.user_settings))
1494
+ # Cache file state
1495
+ self.loaded_file_states[self.loaded_file_index] = self.get_function_data()
1496
+
1497
+ self.loaded_file_index = (self.loaded_file_index + 1) % len(self.loaded_files)
1498
+ self.loaded_file = self.loaded_files[self.loaded_file_index]
1499
+
1500
+ # If we already have a loaded state for this file
1501
+ if self.loaded_file_states[self.loaded_file_index]:
1502
+ self.set_function_data(self.loaded_file_states[self.loaded_file_index])
1503
+ else:
1504
+ self.set_function_data({}, reset_absent_variables=True)
1505
+ self.load_file(self.loaded_file)
1506
+
1507
+ elif setting == "sheet_next":
1508
+ if not os.path.exists(self.loaded_file):
1509
+ self.notification(self.stdscr, message=f"File {repr(self.loaded_file)} not found.")
1510
+ return None
1511
+ if len(self.sheets) > 1:
1512
+ self.command_stack.append(Command("setting", self.user_settings))
1513
+
1514
+ # Cache sheet state
1515
+ self.sheet_states[self.sheet_index] = self.get_function_data()
1516
+ self.sheet_index = (self.sheet_index + 1) % len(self.sheets)
1517
+ self.sheet_name = self.sheets[self.sheet_index]
1518
+
1519
+ # If we already have a loaded state for this file
1520
+ if self.sheet_states[self.sheet_index]:
1521
+ self.set_function_data(self.sheet_states[self.sheet_index])
1522
+ else:
1523
+ function_data = {
1524
+ "sheet_index": self.sheet_index,
1525
+ "sheet_name": self.sheet_name,
1526
+ "sheet_states":self.sheet_states,
1527
+ "sheets": self.sheets,
1528
+ }
1529
+ self.set_function_data(function_data, reset_absent_variables=True)
1530
+ self.load_sheet(self.loaded_file, sheet_number=self.sheet_index)
1531
+
1399
1532
 
1400
1533
  elif setting.startswith("ft"):
1401
1534
  if len(setting) > 2 and setting[2:].isnumeric():
@@ -1463,9 +1596,9 @@ class Picker:
1463
1596
  self.user_settings = ""
1464
1597
  return None
1465
1598
 
1466
-
1467
- self.command_stack.append(Command("setting", self.user_settings))
1468
- self.user_settings = ""
1599
+ if self.user_settings:
1600
+ self.command_stack.append(Command("setting", self.user_settings))
1601
+ self.user_settings = ""
1469
1602
 
1470
1603
  def apply_command(self, command: Command):
1471
1604
  self.logger.info(f"function: apply_command()")
@@ -1773,17 +1906,41 @@ class Picker:
1773
1906
  ]
1774
1907
 
1775
1908
  if s:
1776
- file_to_load = file_picker()
1777
- if file_to_load:
1909
+ restrict_curses(self.stdscr)
1910
+ files_to_load = file_picker()
1911
+ unrestrict_curses(self.stdscr)
1912
+ if files_to_load:
1778
1913
  index = list(s.keys())[0]
1914
+ file_to_load = files_to_load[0]
1779
1915
  return_val = funcs[index](file_to_load)
1780
- self.set_function_data(return_val)
1781
1916
 
1917
+ self.loaded_file_states[self.loaded_file_index] = self.get_function_data()
1918
+
1919
+ self.stdscr.clear()
1920
+ self.draw_screen(self.indexed_items, self.highlights)
1921
+
1922
+ tmp = self.stdscr
1923
+
1924
+ self.loaded_files += files_to_load
1925
+ self.loaded_file_states += [{} for _ in files_to_load]
1926
+ self.loaded_file = file_to_load
1927
+ self.loaded_file_index = len(self.loaded_files)-len(files_to_load)
1928
+
1929
+
1930
+ self.stdscr = tmp
1931
+
1932
+ h, w = self.stdscr.getmaxyx()
1933
+ self.notification(self.stdscr, f"{repr(file_to_load)} has been loaded!")
1934
+
1935
+ self.set_function_data({}, reset_absent_variables=True)
1936
+ self.load_file(self.loaded_file)
1782
1937
  # items = return_val["items"]
1783
1938
  # header = return_val["header"]
1784
- self.initialise_variables()
1939
+ self.stdscr.clear()
1940
+ # self.initialise_variables()
1785
1941
  self.draw_screen(self.indexed_items, self.highlights)
1786
- self.notification(self.stdscr, f"{repr(file_to_load)} has been loaded!")
1942
+ # self.stdscr.refresh()
1943
+
1787
1944
  # if return_val:
1788
1945
  # notification(stdscr, message=return_val, title="Error")
1789
1946
 
@@ -1915,6 +2072,36 @@ class Picker:
1915
2072
  self.cursor_pos = current_cursor_pos
1916
2073
 
1917
2074
 
2075
+ def load_file(self, filename: str) -> None:
2076
+ if not os.path.exists(filename):
2077
+ self.notification(self.stdscr, message = f"File not found: {filename}")
2078
+ return None
2079
+
2080
+ filetype = guess_file_type(filename)
2081
+ items, header, sheets = table_to_list(filename, file_type=filetype)
2082
+
2083
+ if items != None:
2084
+ self.items = items
2085
+ self.header = header if header != None else []
2086
+ self.sheets = sheets
2087
+
2088
+
2089
+ self.initialise_variables()
2090
+
2091
+ def load_sheet(self, filename: str, sheet_number: int = 0):
2092
+ filetype = guess_file_type(filename)
2093
+ items, header, sheets = table_to_list(filename, file_type=filetype, sheet_number=sheet_number)
2094
+
2095
+ if items != None:
2096
+ self.items = items
2097
+ self.header = header if header != None else []
2098
+ self.sheets = sheets
2099
+
2100
+ self.initialise_variables()
2101
+
2102
+
2103
+
2104
+
1918
2105
 
1919
2106
  def run(self) -> Tuple[list[int], str, dict]:
1920
2107
  """ Run the picker. """
@@ -1963,7 +2150,7 @@ class Picker:
1963
2150
 
1964
2151
  while True:
1965
2152
  key = self.stdscr.getch()
1966
- if key:
2153
+ if key != -1:
1967
2154
  self.logger.info(f"key={key}")
1968
2155
  h, w = self.stdscr.getmaxyx()
1969
2156
  if key in self.disabled_keys: continue
@@ -2042,12 +2229,130 @@ class Picker:
2042
2229
  }
2043
2230
  OptionPicker = Picker(self.stdscr, **help_data)
2044
2231
  s, o, f = OptionPicker.run()
2232
+ self.draw_screen(self.indexed_items, self.highlights)
2233
+
2234
+ if self.check_key("info", key, self.keys_dict):
2235
+ self.logger.info(f"key_function help")
2236
+ self.stdscr.clear()
2237
+ self.stdscr.refresh()
2238
+ import importlib.metadata as metadata
2239
+ version = metadata.version('listpick')
2240
+
2241
+ info_items = [
2242
+ [" Listpick info:", "-*"*30],
2243
+ ["",""],
2244
+ ["listpick version", f"{version}"],
2245
+
2246
+ ["",""],
2247
+ [" Global:", "-*"*30],
2248
+ ["",""],
2249
+ ["current_file", self.loaded_file],
2250
+ ["loaded_files", repr(self.loaded_files)],
2251
+ ["auto_refresh", f"{repr(self.auto_refresh)}"],
2252
+ ["timer", f"{repr(self.timer)}"],
2253
+ ["pin_cursor", f"{repr(self.pin_cursor)}"],
2254
+ ["cwd", f"{os.getcwd()}"],
2255
+ ["Picker memory", f"{format_size(sys.getsizeof(self))}"],
2256
+ ["debug", f"{repr(self.debug)}"],
2257
+ ["debug level", f"{repr(self.debug_level)}"],
2258
+
2259
+ ["",""],
2260
+ [" Current File:", "-*"*30],
2261
+ ["",""],
2262
+ ["row/row count", f"{self.cursor_pos}/{len(self.indexed_items)}"],
2263
+ ["total rows", f"{len(self.items)}"],
2264
+ ["selections", f"{self.selected_cells_by_row}"],
2265
+ ["current_sheet", self.sheet_name],
2266
+ ["sheets", repr(self.sheets)],
2267
+ ["current column/column_count", f"{self.selected_column}/{len(self.column_widths)}"],
2268
+ ["hidden columns", f"{self.hidden_columns}"],
2269
+ ["sort column", f"{self.sort_column}"],
2270
+ ["sort method", f"{self.SORT_METHODS[self.columns_sort_method[self.sort_column]]}"],
2271
+ ["sort order", f"{'Descending' if self.sort_reverse[self.sort_column] else 'Ascending'}"],
2272
+ ["id_column", f"{self.id_column}"],
2273
+
2274
+ ["",""],
2275
+ [" Display options:", "-*"*30],
2276
+ ["",""],
2277
+ ["show_header", str(self.show_header)],
2278
+ ["show_footer", repr(self.show_footer)],
2279
+ ["show_row_header", repr(self.show_row_header)],
2280
+ ["max_column_width", str(self.max_column_width)],
2281
+ ["colour_theme_number", str(self.colour_theme_number)],
2282
+ ["top_gap", str(self.top_gap)],
2283
+ ["highlight_full_row", repr(self.highlight_full_row)],
2284
+ ["cell_cursor", repr(self.cell_cursor)],
2285
+ ["items_per_page", repr(self.items_per_page)],
2286
+ ["paginate", repr(self.paginate)],
2287
+ ["display_modes", repr(self.display_modes)],
2288
+ ["footer_style", repr(self.footer_style)],
2289
+ ["unicode_char_width", repr(self.unicode_char_width)],
2290
+ ["centre_in_terminal", repr(self.centre_in_terminal)],
2291
+ ["centre_in_cols", repr(self.centre_in_cols)],
2292
+ ["centre_in_terminal_vertical", repr(self.centre_in_terminal_vertical)],
2293
+ ]
2294
+
2295
+ data = self.get_function_data()
2296
+ del data["indexed_items"]
2297
+ del data["selections"]
2298
+ del data["selected_cells_by_row"]
2299
+ del data["cell_selections"]
2300
+ del data["items"]
2301
+ del data["require_option"]
2302
+ del data["option_functions"]
2303
+ info_items += [
2304
+ ["",""],
2305
+ [" get_function_data():", "-*"*30],
2306
+ ["",""],
2307
+ ["show_header", str(self.show_header)],
2308
+ ]
2309
+ info_items += [[key, repr(value)] for key, value in data.items()]
2310
+ info_header = ["Option", "Value"]
2311
+ info_data = {
2312
+ "items": info_items,
2313
+ "header": info_header,
2314
+ "title": f"{self.title} Info",
2315
+ "colours_start": self.help_colours_start,
2316
+ "colours": help_colours,
2317
+ "show_footer": True,
2318
+ "max_selected": 1,
2319
+ "keys_dict": help_keys,
2320
+ "disabled_keys": [ord('?'), ord('v'), ord('V'), ord('m'), ord('M'), ord('l'), curses.KEY_ENTER, ord('\n')],
2321
+ "highlight_full_row": True,
2322
+ "top_gap": 0,
2323
+ "paginate": self.paginate,
2324
+ "centre_in_terminal": False,
2325
+ "centre_in_terminal_vertical": True,
2326
+ "hidden_columns": [],
2327
+ "reset_colours": False,
2328
+
2329
+ }
2330
+ OptionPicker = Picker(self.stdscr, **info_data)
2331
+ s, o, f = OptionPicker.run()
2332
+
2333
+ self.draw_screen(self.indexed_items, self.highlights)
2045
2334
 
2046
2335
  elif self.check_key("exit", key, self.keys_dict):
2047
2336
  self.stdscr.clear()
2048
- function_data = self.get_function_data()
2049
- function_data["last_key"] = key
2050
- return [], "", function_data
2337
+ if len(self.loaded_files) <= 1:
2338
+ function_data = self.get_function_data()
2339
+ function_data["last_key"] = key
2340
+ return [], "", function_data
2341
+ else:
2342
+ del self.loaded_files[self.loaded_file_index]
2343
+ del self.loaded_file_states[self.loaded_file_index]
2344
+ self.loaded_file_index = min(self.loaded_file_index, len(self.loaded_files)-1)
2345
+ self.loaded_file = self.loaded_files[self.loaded_file_index]
2346
+
2347
+
2348
+ # If we already have a loaded state for this file
2349
+ if self.loaded_file_states[self.loaded_file_index]:
2350
+ self.set_function_data(self.loaded_file_states[self.loaded_file_index])
2351
+ else:
2352
+ self.set_function_data({}, reset_absent_variables=True)
2353
+ self.load_file(self.loaded_file)
2354
+ self.draw_screen(self.indexed_items, self.highlights)
2355
+
2051
2356
  elif self.check_key("full_exit", key, self.keys_dict):
2052
2357
  close_curses(self.stdscr)
2053
2358
  exit()
@@ -2612,6 +2917,7 @@ class Picker:
2612
2917
  # 4. if self.cancel_is_back (e.g., notification) then we exit
2613
2918
  # 4. selecting
2614
2919
 
2920
+ pass
2615
2921
  # Cancel visual de/selection
2616
2922
  if self.is_selecting or self.is_deselecting:
2617
2923
  self.start_selection = -1
@@ -2689,41 +2995,51 @@ class Picker:
2689
2995
 
2690
2996
  elif self.check_key("mode_next", key, self.keys_dict): # tab key
2691
2997
  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
2998
+ if len(self.modes):
2999
+ prev_mode_index = self.mode_index
3000
+ self.mode_index = (self.mode_index+1)%len(self.modes)
3001
+ mode = self.modes[self.mode_index]
3002
+ for key, val in mode.items():
3003
+ if key == 'filter':
3004
+ if 'filter' in self.modes[prev_mode_index]:
3005
+ self.filter_query = self.filter_query.replace(self.modes[prev_mode_index]['filter'], '')
3006
+ self.filter_query = f"{self.filter_query.strip()} {val.strip()}".strip()
3007
+ prev_index = self.indexed_items[self.cursor_pos][0] if len(self.indexed_items)>0 else 0
3008
+
3009
+ self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
3010
+ 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)
3011
+ else: new_index = 0
3012
+ self.cursor_pos = new_index
3013
+ # Re-sort self.items after applying filter
3014
+ 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
3015
  elif self.check_key("mode_prev", key, self.keys_dict): # shift+tab key
2710
3016
  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
3017
+ if len(self.modes):
3018
+ prev_mode_index = self.mode_index
3019
+ self.mode_index = (self.mode_index-1)%len(self.modes)
3020
+ mode = self.modes[self.mode_index]
3021
+ for key, val in mode.items():
3022
+ if key == 'filter':
3023
+ if 'filter' in self.modes[prev_mode_index]:
3024
+ self.filter_query = self.filter_query.replace(self.modes[prev_mode_index]['filter'], '')
3025
+ self.filter_query = f"{self.filter_query.strip()} {val.strip()}".strip()
3026
+ prev_index = self.indexed_items[self.cursor_pos][0] if len(self.indexed_items)>0 else 0
3027
+ self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
3028
+ 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)
3029
+ else: new_index = 0
3030
+ self.cursor_pos = new_index
3031
+ # Re-sort self.items after applying filter
3032
+ 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
3033
+ elif self.check_key("file_next", key, self.keys_dict):
3034
+ if len(self.loaded_files):
3035
+ self.loaded_file_index = (self.loaded_file_index + 1) % len(self.loaded_files)
3036
+ self.loaded_file = self.loaded_files[self.loaded_file_index]
3037
+
3038
+ elif self.check_key("file_prev", key, self.keys_dict):
3039
+ if len(self.loaded_files):
3040
+ self.loaded_file_index = (self.loaded_file_index - 1) % len(self.loaded_files)
3041
+ self.loaded_file = self.loaded_files[self.loaded_file_index]
3042
+
2727
3043
  elif self.check_key("pipe_input", key, self.keys_dict):
2728
3044
  self.logger.info(f"key_function pipe_input")
2729
3045
  # usrtxt = "xargs -d '\n' -I{} "
@@ -3036,7 +3352,8 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
3036
3352
  """ Parse command line arguments. """
3037
3353
  parser = argparse.ArgumentParser(description='Convert table to list of lists.')
3038
3354
  # 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.')
3355
+ # parser.add_argument('-i', dest='file', help='File containing the table to be converted.')
3356
+ parser.add_argument('-i', dest='file', nargs='+', help='File containing the table to be converted.')
3040
3357
  parser.add_argument('--load', '-l', dest='load', type=str, help='Load file from Picker dump.')
3041
3358
  parser.add_argument('--stdin', dest='stdin', action='store_true', help='Table passed on stdin')
3042
3359
  parser.add_argument('--stdin2', action='store_true', help='Table passed on stdin')
@@ -3058,7 +3375,8 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
3058
3375
  }
3059
3376
 
3060
3377
  if args.file:
3061
- input_arg = args.file
3378
+ input_arg = args.file[0]
3379
+
3062
3380
  elif args.stdin:
3063
3381
  input_arg = '--stdin'
3064
3382
  elif args.stdin2:
@@ -3096,9 +3414,14 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
3096
3414
  filetype = args.file_type
3097
3415
 
3098
3416
 
3099
- items, header = table_to_list(input_arg, args.delimiter, filetype)
3417
+ items, header, sheets = table_to_list(input_arg, args.delimiter, filetype)
3100
3418
  function_data["items"] = items
3101
3419
  if header: function_data["header"] = header
3420
+ function_data["sheets"] = sheets
3421
+ if args.file:
3422
+ function_data["loaded_file"] = args.file[0]
3423
+ function_data["loaded_files"] = args.file
3424
+
3102
3425
  return args, function_data
3103
3426
 
3104
3427
  def start_curses() -> curses.window:
@@ -3188,7 +3511,8 @@ def main() -> None:
3188
3511
  # function_data["infobox_title"] = "Title"
3189
3512
  # function_data["footer_string"] = "Title"
3190
3513
  function_data["highlights"] = highlights
3191
- function_data["show_footer"] = False
3514
+ # function_data["show_footer"] = False
3515
+ # function_data["paginate"] = True
3192
3516
  # function_data["debug"] = True
3193
3517
  # function_data["debug_level"] = 1
3194
3518
  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'])} 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
 
listpick/ui/keys.py CHANGED
@@ -47,8 +47,8 @@ picker_keys = {
47
47
  "search_input": [ord('/')],
48
48
  "settings_input": [ord('`')],
49
49
  "settings_options": [ord('~')],
50
- "continue_search_forward": [ord('n'), ord('i')],
51
- "continue_search_backward": [ord('N'), ord('I')],
50
+ "continue_search_forward": [ord('n')],
51
+ "continue_search_backward": [ord('N')],
52
52
  "cancel": [27], # Escape key
53
53
  "opts_input": [ord(':')],
54
54
  "opts_select": [ord('o')],
@@ -80,6 +80,7 @@ picker_keys = {
80
80
  "add_column_after": [ord('+')],
81
81
  # "add_row_before": [ord('=')],
82
82
  "add_row_after": [ord('=')],
83
+ "info": [ord('i')],
83
84
  }
84
85
 
85
86
 
@@ -106,8 +107,8 @@ help_keys = {
106
107
  "search_input": [ord('/')],
107
108
  "settings_input": [ord('`')],
108
109
  "settings_options": [ord('~')],
109
- "continue_search_forward": [ord('n'), ord('i')],
110
- "continue_search_backward": [ord('N'), ord('I')],
110
+ "continue_search_forward": [ord('n')],
111
+ "continue_search_backward": [ord('N')],
111
112
  "cancel": [27], # Escape key
112
113
  "col_select": [ord('0'), ord('1'), ord('2'), ord('3'), ord('4'), ord('5'), ord('6'), ord('7'), ord('8'), ord('9')],
113
114
  "col_select_next": [ord('>')],
@@ -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.')
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
- 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.1
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
@@ -1,12 +1,12 @@
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=OE6Py8M0R0TVAb72SCiCk9MSc3TLh4ZgO2qe5xBtNro,166710
3
+ listpick/listpick_app.py,sha256=-QdN2nv3Aii_vJdt8ROOkL_v-mI2TNQi1q34f5tGAno,181819
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=s1L68MNmhWwbWRy0mn0ChmnE_dMQBAzNlTv917pyHE0,10673
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
- listpick/ui/keys.py,sha256=TzaadgBP_rC7jbp--RFJZDOkHd0EB4K1wToDTiVs6CI,13029
9
+ listpick/ui/keys.py,sha256=8LRO0CqX8tn6LM9irwSK7eA_Go-v0nBDztWtQKPFXUk,13042
10
10
  listpick/ui/pane_stuff.py,sha256=7GXa4UnV_7YmBv-baRi5moN51wYcuS4p0odl5C3m0Tc,169
11
11
  listpick/ui/picker_colours.py,sha256=FLOzvkq83orrN2bL0Mw-6RugWOZyuwUjQCrUFMUnKGY,11563
12
12
  listpick/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -21,11 +21,11 @@ listpick/utils/picker_log.py,sha256=SW6GmjxpI7YrSf72fSr4O8Ux0fY_OzaSXUgTFdz6Xo4,
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=T-i-nV1p6g8UagdgUPKrhIGpKY_YXZDxf4xZzcPepNA,7635
25
- listpick/utils/utils.py,sha256=3La3uyij4QazWV_8GpXJ7nFFMHY2sRTW2OWXJTX-LeY,13791
26
- listpick-0.1.13.62.dist-info/licenses/LICENSE.txt,sha256=2mP-MRHJptADDNE9VInMNg1tE-C6Qv93Z4CCQKrpg9w,1061
27
- listpick-0.1.13.62.dist-info/METADATA,sha256=YNt1kbQKoIp0L_kCs8GZwEWPbt6hDquSrq4GmDlpoW0,8053
28
- listpick-0.1.13.62.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
- listpick-0.1.13.62.dist-info/entry_points.txt,sha256=-QCf_BKIkUz35Y9nkYpjZWs2Qg0KfRna2PAs5DnF6BE,43
30
- listpick-0.1.13.62.dist-info/top_level.txt,sha256=5mtsGEz86rz3qQDe0D463gGjAfSp6A3EWg4J4AGYr-Q,9
31
- listpick-0.1.13.62.dist-info/RECORD,,
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.1.dist-info/licenses/LICENSE.txt,sha256=2mP-MRHJptADDNE9VInMNg1tE-C6Qv93Z4CCQKrpg9w,1061
27
+ listpick-0.1.14.1.dist-info/METADATA,sha256=pEzvDtilAXf9X5luuKJXLfL25sHxBiaVAVrirREgt0I,8090
28
+ listpick-0.1.14.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
+ listpick-0.1.14.1.dist-info/entry_points.txt,sha256=-QCf_BKIkUz35Y9nkYpjZWs2Qg0KfRna2PAs5DnF6BE,43
30
+ listpick-0.1.14.1.dist-info/top_level.txt,sha256=5mtsGEz86rz3qQDe0D463gGjAfSp6A3EWg4J4AGYr-Q,9
31
+ listpick-0.1.14.1.dist-info/RECORD,,