listpick 0.1.14.5__py3-none-any.whl → 0.1.14.7__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
@@ -110,7 +110,7 @@ class Picker:
110
110
  cell_selections: dict[tuple[int,int], bool] = {},
111
111
  selected_cells_by_row: dict = {},
112
112
  highlight_full_row: bool =False,
113
- cell_cursor: bool = False,
113
+ cell_cursor: bool = True,
114
114
 
115
115
  items_per_page : int = -1,
116
116
  sort_method : int = 0,
@@ -352,9 +352,17 @@ class Picker:
352
352
  size += sys.getsizeof(getattr(self, attr_name))
353
353
  return size
354
354
 
355
- def set_config(self, path: str ="~/.config/listpick/config.toml"):
355
+ def set_config(self, path: str ="~/.config/listpick/config.toml") -> bool:
356
356
  """ Set config from toml file. """
357
- config = self.get_config(path)
357
+ path = os.path.expanduser(os.path.expandvars(path))
358
+ if not os.path.exists(path):
359
+ return False
360
+ try:
361
+ config = self.get_config(path)
362
+ except Exception as e:
363
+ self.logger.error(f"get_config({path}) load error. {e}")
364
+ return False
365
+
358
366
  self.logger.info(f"function: set_config()")
359
367
  if "general" in config:
360
368
  for key, val in config["general"].items():
@@ -363,53 +371,17 @@ class Picker:
363
371
  setattr(self, key, val)
364
372
  except Exception as e:
365
373
  self.logger.error(f"set_config: key={key}, val={val}. {e}")
374
+ return True
375
+
366
376
 
367
377
 
368
378
  def get_config(self, path: str ="~/.config/listpick/config.toml") -> dict:
369
379
  """ Get config from file. """
370
380
  self.logger.info(f"function: get_config()")
371
381
  import toml
372
- if os.path.exists(os.path.expanduser(path)):
373
- with open(os.path.expanduser(path), "r") as f:
374
- config = toml.load(f)
375
- return config
376
- # full_config = self.get_default_config()
377
- # default_path = "~/.config/listpick/config.toml"
378
- #
379
- # CONFIGPATH = default_path
380
-
381
-
382
-
383
- # if "general" in config:
384
- # for key in config["general"]:
385
- # full_config["general"][key] = config["general"][key]
386
- # if "appearance" in config:
387
- # for key in config["appearance"]:
388
- # full_config["appearance"][key] = config["appearance"][key]
389
- #
390
- # return full_config
391
-
392
- def get_default_config(self) -> dict:
393
- default_config = {
394
- "general" : {
395
- "url": "http://localhost",
396
- "port": "6800",
397
- "token": "",
398
- "startupcmds": ["aria2c"],
399
- "restartcmds": ["pkill aria2c && sleep 1 && aria2c"],
400
- "ariaconfigpath": "~/.config/aria2/aria2.conf",
401
- "paginate": False,
402
- "refresh_timer": 2,
403
- "global_stats_timer": 1,
404
- "terminal_file_manager": "yazi",
405
- "gui_file_manager": "kitty yazi",
406
- "launch_command": "xdg-open",
407
- },
408
- "appearance":{
409
- "theme": 0
410
- }
411
- }
412
- return default_config
382
+ with open(os.path.expanduser(path), "r") as f:
383
+ config = toml.load(f)
384
+ return config
413
385
 
414
386
  def calculate_section_sizes(self):
415
387
  """
@@ -2018,10 +1990,12 @@ class Picker:
2018
1990
  self.items, self.header = tmp_items, tmp_header
2019
1991
  self.data_ready = True
2020
1992
 
2021
- def save_input_history(self, file_path: str) -> bool:
1993
+ def save_input_history(self, file_path: str, force_save: bool=True) -> bool:
2022
1994
  """ Save input field history. Returns True if successful save. """
2023
1995
  self.logger.info(f"function: save_input_history()")
2024
1996
  file_path = os.path.expanduser(file_path)
1997
+ file_path = os.path.expandvars(file_path)
1998
+ directory = os.path.dirname(file_path)
2025
1999
  history_dict = {
2026
2000
  "history_filter_and_search" : self.history_filter_and_search,
2027
2001
  "history_pipes" : self.history_pipes,
@@ -2029,8 +2003,10 @@ class Picker:
2029
2003
  "history_edits" : self.history_edits,
2030
2004
  "history_settings": self.history_settings,
2031
2005
  }
2032
- with open(file_path, 'w') as f:
2033
- json.dump(history_dict, f)
2006
+ if os.path.exists(directory) or force_save:
2007
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
2008
+ with open(file_path, 'w') as f:
2009
+ json.dump(history_dict, f)
2034
2010
 
2035
2011
  return True
2036
2012
 
@@ -2128,27 +2104,32 @@ class Picker:
2128
2104
  self.notification(self.stdscr, message = f"File not found: {filename}")
2129
2105
  return None
2130
2106
 
2131
- filetype = guess_file_type(filename)
2132
- items, header, sheets = table_to_list(filename, file_type=filetype)
2107
+ try:
2108
+ filetype = guess_file_type(filename)
2109
+ items, header, sheets = table_to_list(filename, file_type=filetype)
2133
2110
 
2134
- if items != None:
2135
- self.items = items
2136
- self.header = header if header != None else []
2137
- self.sheets = sheets
2111
+ if items != None:
2112
+ self.items = items
2113
+ self.header = header if header != None else []
2114
+ self.sheets = sheets
2138
2115
 
2139
2116
 
2140
- self.initialise_variables()
2117
+ self.initialise_variables()
2118
+ except Exception as e:
2119
+ self.notification(self.stdscr, message=f"Error loading {filename}: {e}")
2141
2120
 
2142
2121
  def load_sheet(self, filename: str, sheet_number: int = 0):
2143
2122
  filetype = guess_file_type(filename)
2144
- items, header, sheets = table_to_list(filename, file_type=filetype, sheet_number=sheet_number)
2145
-
2146
- if items != None:
2147
- self.items = items
2148
- self.header = header if header != None else []
2149
- self.sheets = sheets
2123
+ try:
2124
+ items, header, sheets = table_to_list(filename, file_type=filetype, sheet_number=sheet_number)
2125
+ if items != None:
2126
+ self.items = items
2127
+ self.header = header if header != None else []
2128
+ self.sheets = sheets
2150
2129
 
2151
- self.initialise_variables()
2130
+ self.initialise_variables()
2131
+ except Exception as e:
2132
+ self.notification(self.stdscr, message=f"Error loading {filename}, sheet {sheet_number}: {e}")
2152
2133
 
2153
2134
  def switch_file(self, increment=1) -> None:
2154
2135
  """ Go to the next file. """
@@ -2161,6 +2142,7 @@ class Picker:
2161
2142
  self.loaded_file_index = (self.loaded_file_index + increment) % len(self.loaded_files)
2162
2143
  self.loaded_file = self.loaded_files[self.loaded_file_index]
2163
2144
 
2145
+ idx, file = self.loaded_file_index, self.loaded_file
2164
2146
  # If we already have a loaded state for this file
2165
2147
  if self.loaded_file_states[self.loaded_file_index]:
2166
2148
  self.set_function_data(self.loaded_file_states[self.loaded_file_index])
@@ -2168,6 +2150,7 @@ class Picker:
2168
2150
  self.set_function_data({}, reset_absent_variables=True)
2169
2151
  self.load_file(self.loaded_file)
2170
2152
 
2153
+ self.loaded_file_index, self.loaded_file = idx, file
2171
2154
 
2172
2155
  def switch_sheet(self, increment=1) -> None:
2173
2156
  if not os.path.exists(self.loaded_file):
@@ -2451,6 +2434,7 @@ class Picker:
2451
2434
  del self.loaded_file_states[self.loaded_file_index]
2452
2435
  self.loaded_file_index = min(self.loaded_file_index, len(self.loaded_files)-1)
2453
2436
  self.loaded_file = self.loaded_files[self.loaded_file_index]
2437
+ idx, file = self.loaded_file_index, self.loaded_file
2454
2438
 
2455
2439
 
2456
2440
  # If we already have a loaded state for this file
@@ -2459,6 +2443,7 @@ class Picker:
2459
2443
  else:
2460
2444
  self.set_function_data({}, reset_absent_variables=True)
2461
2445
  self.load_file(self.loaded_file)
2446
+ self.loaded_file_index, self.loaded_file = idx, file
2462
2447
  self.draw_screen(self.indexed_items, self.highlights)
2463
2448
 
2464
2449
  elif self.check_key("full_exit", key, self.keys_dict):
@@ -3467,10 +3452,11 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
3467
3452
  parser.add_argument('--stdin', dest='stdin', action='store_true', help='Table passed on stdin')
3468
3453
  parser.add_argument('--stdin2', action='store_true', help='Table passed on stdin')
3469
3454
  parser.add_argument('--generate', '-g', type=str, help='Pass file to generate data for listpick Picker.')
3470
- parser.add_argument('-d', dest='delimiter', default='\t', help='Delimiter for rows in the table (default: tab)')
3455
+ parser.add_argument('--delimiter', '-d', dest='delimiter', default='\t', help='Delimiter for rows in the table (default: tab)')
3471
3456
  parser.add_argument('-t', dest='file_type', choices=['tsv', 'csv', 'json', 'xlsx', 'ods', 'pkl'], help='Type of file (tsv, csv, json, xlsx, ods)')
3472
3457
  parser.add_argument('--debug', action="store_true", help="Enable debug log.")
3473
3458
  parser.add_argument('--debug-verbose', action="store_true", help="Enable debug verbose log.")
3459
+ parser.add_argument('--headerless', action="store_false", help="By default the first row is interpreted as a header row. If --headerless is passed then there is no header.")
3474
3460
  args = parser.parse_args()
3475
3461
 
3476
3462
 
@@ -3523,13 +3509,31 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
3523
3509
  filetype = args.file_type
3524
3510
 
3525
3511
 
3526
- items, header, sheets = table_to_list(input_arg, args.delimiter, filetype)
3512
+ while True:
3513
+ try:
3514
+ items, header, sheets = table_to_list(
3515
+ input_arg=input_arg,
3516
+ delimiter=args.delimiter,
3517
+ file_type = filetype,
3518
+ first_row_is_header=args.headerless,
3519
+ )
3520
+ if args.file:
3521
+ function_data["loaded_file"] = args.file[0]
3522
+ function_data["loaded_files"] = args.file
3523
+ break
3524
+
3525
+ except Exception as e:
3526
+ items, header, sheets = [], [], []
3527
+ function_data["startup_notification"] = f"Error loading {input_arg}. {e}"
3528
+ if args.file:
3529
+ args.file = args.file[1:]
3530
+ input_arg = args.file[0]
3531
+ else:
3532
+ break
3533
+
3527
3534
  function_data["items"] = items
3528
3535
  if header: function_data["header"] = header
3529
3536
  function_data["sheets"] = sheets
3530
- if args.file:
3531
- function_data["loaded_file"] = args.file[0]
3532
- function_data["loaded_files"] = args.file
3533
3537
 
3534
3538
  return args, function_data
3535
3539
 
@@ -3577,13 +3581,41 @@ def open_tty():
3577
3581
  tty.setraw(tty_fd)
3578
3582
  return tty_fd
3579
3583
 
3580
- def get_char(tty_fd, timeout: float = 0.2) -> int:
3584
+ def get_char(tty_fd, timeout: float = 0.2, secondary: bool = False) -> int:
3581
3585
  """ Get character from a tty_fd with a timeout. """
3582
3586
  rlist, _, _ = select.select([tty_fd], [], [], timeout)
3583
3587
  if rlist:
3584
3588
  # key = ord(tty_fd.read(1))
3585
3589
  key = ord(os.read(tty_fd, 1))
3586
- # os.system(f"notify-send { key }")
3590
+ if not secondary:
3591
+ if key == 27:
3592
+ key2 = get_char(tty_fd, timeout=0.01, secondary=True)
3593
+ key3 = get_char(tty_fd, timeout=0.01, secondary=True)
3594
+ key4 = get_char(tty_fd, timeout=0.01, secondary=True)
3595
+ key5 = get_char(tty_fd, timeout=0.01, secondary=True)
3596
+ if key2 == ord('O') and key3 == ord('B'):
3597
+ key = curses.KEY_DOWN
3598
+ elif key2 == ord('O') and key3 == ord('A'):
3599
+ key = curses.KEY_UP
3600
+ elif key2 == ord('O') and key3 == ord('D'):
3601
+ key = curses.KEY_LEFT
3602
+ elif key2 == ord('O') and key3 == ord('C'):
3603
+ key = curses.KEY_RIGHT
3604
+ elif key2 == ord('[') and key3 == ord('Z'):
3605
+ key = 353
3606
+ elif key2 == ord('O') and key3 == ord('F'):
3607
+ key = curses.KEY_END
3608
+ elif key2 == ord('O') and key3 == ord('H'):
3609
+ key = curses.KEY_HOME
3610
+ elif key2 == ord('[') and key3 == ord('3') and key4 == ord('~'):
3611
+ key = curses.KEY_DC
3612
+ elif key2 == ord('[') and key3 == ord('3') and key4 == ord('~'):
3613
+ key = curses.KEY_DC
3614
+ elif key2 == ord('O') and key3 == ord('P'):
3615
+ key = curses.KEY_F1
3616
+ elif key2 == ord('[') and key3 == ord('1') and key4 == ord('5') and key5 == ord('~'):
3617
+ key = curses.KEY_F5
3618
+
3587
3619
  else:
3588
3620
  key = -1
3589
3621
  return key
@@ -3661,7 +3693,7 @@ def main() -> None:
3661
3693
  app.load_input_history("~/.config/listpick/cmdhist.json")
3662
3694
  app.run()
3663
3695
 
3664
- app.save_input_history("~/.config/listpick/cmdhist.json")
3696
+ # app.save_input_history("~/.config/listpick/cmdhist.json")
3665
3697
  except Exception as e:
3666
3698
  print(e)
3667
3699
 
listpick/ui/footer.py CHANGED
@@ -94,9 +94,11 @@ class StandardFooter(Footer):
94
94
 
95
95
 
96
96
 
97
+ ## Clear background of footer rows
97
98
  for i in range(self.height):
98
99
  self.stdscr.addstr(h-self.height+i, 0, ' '*(w-1), curses.color_pair(self.colours_start+20))
99
100
 
101
+ # Display loaded files
100
102
  if len(state["loaded_files"]) > 1 and state["loaded_file"] in state["loaded_files"]:
101
103
 
102
104
  sep = "◢ "
@@ -172,17 +174,23 @@ class StandardFooter(Footer):
172
174
 
173
175
 
174
176
 
177
+
178
+ ## Cursor selection mode
175
179
  select_mode = "C"
176
180
  if state["is_selecting"]: select_mode = "VS"
177
181
  elif state["is_deselecting"]: select_mode = "VDS"
178
182
  if state["pin_cursor"]: select_mode = f"{select_mode} "
183
+
179
184
  # Cursor & selection info
180
185
  selected_count = sum(state["selections"].values())
181
186
  if state["paginate"]:
182
- # 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}"
183
187
  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}"
184
188
  else:
185
- cursor_disp_str = f" [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])} | {select_mode}"
189
+ # cursor_disp_str = f" [{selected_count}] {state['cursor_pos']+1},{state['selected_column']}/{len(state['indexed_items'])},{len(state['column_widths'])} | {select_mode}"
190
+ if state["cell_cursor"]:
191
+ cursor_disp_str = f" [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])} | {state['selected_column']}/{len(state['column_widths'])} | {select_mode}"
192
+ else:
193
+ cursor_disp_str = f" [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])} | {select_mode}"
186
194
 
187
195
  # Maximum chars that should be displayed
188
196
  max_chars = min(len(cursor_disp_str)+2, w)
@@ -31,7 +31,6 @@ def get_char(tty_fd, timeout: float = 0.2) -> int:
31
31
  if rlist:
32
32
  # key = ord(tty_fd.read(1))
33
33
  key = ord(os.read(tty_fd, 1))
34
- # os.system(f"notify-send { key }")
35
34
  else:
36
35
  key = -1
37
36
  return key
@@ -125,10 +124,8 @@ def input_field(
125
124
  offscreen_x, offscreen_y = False, False
126
125
  orig_x, orig_y = x, y
127
126
 
128
- # tty_fd = open('/dev/tty')
129
- # tty_fd = os.open('/dev/tty', os.O_RDONLY)
130
- # tty.setraw(tty_fd)
131
127
  tty_fd = open_tty()
128
+
132
129
  # Input field loop
133
130
  while True:
134
131
 
@@ -230,28 +227,13 @@ def input_field(
230
227
 
231
228
 
232
229
  stdscr.refresh()
233
- # timeout = 0.05
234
- # rlist, _, _ = select.select([tty_fd], [], [], timeout)
235
- # if rlist:
236
- # # key = ord(tty_fd.read(1))
237
- # key = ord(os.read(tty_fd, 1))
238
- # os.system(f"notify-send { key }")
239
- # else:
240
- # key = -1
241
230
  key = get_char(tty_fd, timeout=0.5)
242
- # key = ord(tty.read(1))
243
231
  # key = stdscr.getch()
244
232
 
245
233
  if key in [27, 7]: # ESC/ALT key or Ctrl+g
246
234
  # For Alt-key combinations: set nodelay and get the second key
247
235
  # stdscr.nodelay(True)
248
236
  # key2 = stdscr.getch()
249
- # rlist, _, _ = select.select([tty_fd], [], [], timeout)
250
- # if rlist:
251
- # key2 = ord(os.read(tty_fd, 1))
252
- # os.system(f"notify-send 'metakey { key }'")
253
- # else:
254
- # key2 = -1
255
237
  key2 = get_char(tty_fd, timeout=0.05)
256
238
 
257
239
  if key2 == -1: # ESCAPE key (no key-combination)
@@ -0,0 +1,70 @@
1
+ # Define constants for alt+a to alt+z
2
+ META_A = 1000
3
+ META_B = 1001
4
+ META_C = 1002
5
+ META_D = 1003
6
+ META_E = 1004
7
+ META_F = 1005
8
+ META_G = 1006
9
+ META_H = 1007
10
+ META_I = 1008
11
+ META_J = 1009
12
+ META_K = 1010
13
+ META_L = 1011
14
+ META_M = 1012
15
+ META_N = 1013
16
+ META_O = 1014
17
+ META_P = 1015
18
+ META_Q = 1016
19
+ META_R = 1017
20
+ META_S = 1018
21
+ META_T = 1019
22
+ META_U = 1020
23
+ META_V = 1021
24
+ META_W = 1022
25
+ META_X = 1023
26
+ META_Y = 1024
27
+ META_Z = 1025
28
+
29
+ # Define constants for alt+A to alt+Z (using uppercase)
30
+ META_a = 1050
31
+ META_b = 1051
32
+ META_c = 1052
33
+ META_d = 1053
34
+ META_e = 1054
35
+ META_f = 1055
36
+ META_g = 1056
37
+ META_h = 1057
38
+ META_i = 1058
39
+ META_j = 1059
40
+ META_k = 1060
41
+ META_l = 1061
42
+ META_m = 1062
43
+ META_n = 1063
44
+ META_o = 1064
45
+ META_p = 1065
46
+ META_q = 1066
47
+ META_r = 1067
48
+ META_s = 1068
49
+ META_t = 1069
50
+ META_u = 1070
51
+ META_v = 1071
52
+ META_w = 1072
53
+ META_x = 1073
54
+ META_y = 1074
55
+ META_z = 1075
56
+
57
+ # Dictionary to map character to its constant value
58
+ META_KEY_MAP = {
59
+ 'a': META_a, 'b': META_b, 'c': META_c, 'd': META_d, 'e': META_e,
60
+ 'f': META_f, 'g': META_g, 'h': META_h, 'i': META_i, 'j': META_j,
61
+ 'k': META_k, 'l': META_l, 'm': META_m, 'n': META_n, 'o': META_o,
62
+ 'p': META_p, 'q': META_q, 'r': META_r, 's': META_s, 't': META_t,
63
+ 'u': META_u, 'v': META_v, 'w': META_w, 'x': META_x, 'y': META_y,
64
+ 'z': META_z, 'A': META_A, 'B': META_B, 'C': META_C, 'D': META_D,
65
+ 'E': META_E, 'F': META_F, 'G': META_G, 'H': META_H, 'I': META_I,
66
+ 'J': META_J, 'K': META_K, 'L': META_L, 'M': META_M, 'N': META_N,
67
+ 'O': META_O, 'P': META_P, 'Q': META_Q, 'R': META_R, 'S': META_S,
68
+ 'T': META_T, 'U': META_U, 'V': META_V, 'W': META_W, 'X': META_X,
69
+ 'Y': META_Y, 'Z': META_Z
70
+ }
listpick/ui/keys.py CHANGED
@@ -122,7 +122,7 @@ help_keys = {
122
122
 
123
123
 
124
124
  notification_keys = {
125
- "exit": [ord('q'), ord('h'), curses.KEY_ENTER, ord('\n'), ord(' '), 27],
125
+ "exit": [ord('q'), ord('h'), curses.KEY_ENTER, ord('\n'), ord(' '), 27, 13],
126
126
  "full_exit": [3], # Ctrl+c
127
127
  "cursor_down": [ord('j'), curses.KEY_DOWN],
128
128
  "cursor_up": [ord('k'), curses.KEY_UP],
@@ -36,7 +36,7 @@ def strip_whitespace(item: Iterable) -> Iterable:
36
36
  return item
37
37
 
38
38
 
39
- def xlsx_to_list(file_name: str, sheet_number:int = 0, extract_formulae: bool = False):
39
+ def xlsx_to_list(file_name: str, sheet_number:int = 0, extract_formulae: bool = False, first_row_is_header: bool = True):
40
40
  import pandas as pd
41
41
  from openpyxl import load_workbook
42
42
  # wb = load_workbook(filename=input_arg, read_only=True)
@@ -56,8 +56,11 @@ def xlsx_to_list(file_name: str, sheet_number:int = 0, extract_formulae: bool =
56
56
  table_data = [[cell if cell != None else "" for cell in row] for row in ws.iter_rows(min_row=1, values_only=True)]
57
57
  header = []
58
58
  # header = [cell for cell in list(ws.iter_rows(values_only=True))[0]] # Assuming the first row is the header
59
- header = table_data[0]
60
- table_data = table_data[1:]
59
+ if first_row_is_header and len(table_data) > 1:
60
+ header = table_data[0]
61
+ table_data = table_data[1:]
62
+ else:
63
+ header = []
61
64
  #
62
65
  # for row in ws.iter_rows(min_row=2, values_only=True): # Skip the header row
63
66
  # row_data = []
@@ -101,7 +104,7 @@ def ods_to_list(filename: str, sheet_number: int = 0, extract_formulas: bool = F
101
104
  cell_text += str(p.firstChild) if p.firstChild is not None else ""
102
105
  row_data.append(cell_text)
103
106
  data.append(row_data)
104
- if first_row_is_header and len(data) > 0:
107
+ if first_row_is_header and len(data) > 1:
105
108
  header = data[0]
106
109
  data = data[1:]
107
110
  else:
@@ -109,7 +112,7 @@ def ods_to_list(filename: str, sheet_number: int = 0, extract_formulas: bool = F
109
112
 
110
113
  return data, header, sheet_names
111
114
 
112
- def ods_to_list_old(file_name: str, sheet_number:int = 0, extract_formulae: bool = False):
115
+ def ods_to_list_old(file_name: str, sheet_number:int = 0, extract_formulae: bool = False, first_row_is_header: bool = True):
113
116
  try:
114
117
  import pandas as pd
115
118
  ef = pd.ExcelFile(file_name)
@@ -127,6 +130,11 @@ def ods_to_list_old(file_name: str, sheet_number:int = 0, extract_formulae: bool
127
130
  header = list(df.columns)
128
131
  except:
129
132
  header = []
133
+
134
+ if not first_row_is_header and header:
135
+ table_data = [header] + table_data
136
+ header = []
137
+
130
138
  return table_data, header, sheets
131
139
  except Exception as e:
132
140
  print(f"Error loading ODS file: {e}")
@@ -134,11 +142,11 @@ def ods_to_list_old(file_name: str, sheet_number:int = 0, extract_formulae: bool
134
142
 
135
143
 
136
144
  def table_to_list(
137
-
138
- input_arg: str,
139
- delimiter:str='\t',
140
- file_type:Optional[str]=None,
141
- sheet_number:int = 0,
145
+ input_arg: str,
146
+ delimiter:str='\t',
147
+ file_type:Optional[str]=None,
148
+ sheet_number:int = 0,
149
+ first_row_is_header:bool = True,
142
150
 
143
151
  ) -> Tuple[list[list[str]], list[str], list[str]]:
144
152
  """
@@ -158,29 +166,54 @@ def table_to_list(
158
166
  logger.info("function: table_to_list (table_to_list_of_lists.py)")
159
167
  table_data = []
160
168
 
161
- def parse_csv_like(data:str, delimiter:str) -> list[list[str]]:
169
+ def parse_csv_like2(data:str, delimiter:str) -> list[list[str]]:
162
170
  """ Convert value-separated data (e.g., CSV or TSV) to list of lists. """
163
171
  logger.info("function: parse_csv_like (table_to_list_of_lists.py)")
164
172
 
165
173
  try:
166
- reader = csv.reader(StringIO(data), delimiter=delimiter)
174
+ # reader = csv.reader(StringIO(data), delimiter=delimiter)
175
+ reader = csv.reader(StringIO(data), dialect='unix')
167
176
  return [row for row in reader]
168
177
  except Exception as e:
169
178
  print(f"Error reading CSV-like input: {e}")
170
179
  return []
180
+ def parse_csv_like(data:str, delimiter: str=" "):
181
+ import re
182
+ def split_columns(line):
183
+ # Define the regex pattern to match quoted strings and split by whitespace
184
+ # pattern = r"(?:'[^']*'|[^'\s]+)"
185
+ pattern = r"(?:\"[^\"]*\"|'[^']*'|[^'\s]+)"
186
+
187
+ # Find all matches using the defined pattern
188
+ columns = re.findall(pattern, line)
189
+
190
+ return columns
171
191
 
172
- def csv_string_to_list(csv_string:str) -> list[list[str]]:
192
+ lines = data.strip().split('\n')
193
+ result = []
194
+
195
+ for line in lines:
196
+ result.append(split_columns(line))
197
+
198
+ return result
199
+
200
+ def csv_string_to_list(csv_string:str, first_row_is_header: bool = True) -> list[list[str]]:
173
201
  """ Convert csv string to list of lists using csv.reader. """
174
202
  logger.info("function: csv_string_to_list (table_to_list_of_lists.py)")
175
203
  f = StringIO(csv_string)
176
204
  reader = csv.reader(f, skipinitialspace=True)
177
- return [row for row in reader]
205
+ table_data = [row for row in reader]
206
+ if first_row_is_header and len(table_data) > 1:
207
+ header = table_data[0]
208
+ table_data = table_data[1:]
209
+ else:
210
+ header = []
211
+ return table_data, header
212
+
178
213
 
179
214
  if input_arg == '--stdin':
180
- os.system(f"notify-send stdin")
181
215
  input_data = sys.stdin.read()
182
216
  elif input_arg == '--stdin2':
183
- os.system(f"notify-send stdin2")
184
217
  input_count = int(sys.stdin.readline())
185
218
  input_data = "\n".join([sys.stdin.readline() for i in range(input_count)])
186
219
  sys.stdin.flush()
@@ -196,10 +229,11 @@ def table_to_list(
196
229
  else:
197
230
  input_data = read_file_content(input_arg)
198
231
 
199
- table_data = csv_string_to_list(input_data)
232
+ table_data, header = csv_string_to_list(input_data, first_row_is_header)
200
233
  table_data = strip_whitespace(table_data)
234
+ header = strip_whitespace([header])[0]
201
235
  # table_data = parse_csv_like(input_data, ",")
202
- return table_data, [], []
236
+ return table_data, header, []
203
237
  except Exception as e:
204
238
  print(f"Error reading CSV/TSV input: {e}")
205
239
  return [], [], []
@@ -248,11 +282,11 @@ def table_to_list(
248
282
 
249
283
  elif file_type == 'xlsx':
250
284
  extract_formulae = False
251
- return xlsx_to_list(input_arg, sheet_number, extract_formulae)
285
+ return xlsx_to_list(input_arg, sheet_number, extract_formulae, first_row_is_header)
252
286
 
253
287
  elif file_type == 'ods':
254
288
  extract_formulae = False
255
- return ods_to_list(input_arg, sheet_number, extract_formulae)
289
+ return ods_to_list(input_arg, sheet_number, extract_formulae, first_row_is_header)
256
290
  elif file_type == 'pkl':
257
291
  with open(os.path.expandvars(os.path.expanduser(input_arg)), 'rb') as f:
258
292
  loaded_data = pickle.load(f)
@@ -264,8 +298,14 @@ def table_to_list(
264
298
  input_data = read_file_content(input_arg)
265
299
 
266
300
  table_data = parse_csv_like(input_data, delimiter)
301
+ if first_row_is_header and len(table_data) > 1:
302
+ header = table_data[0]
303
+ table_data = table_data[1:]
304
+ else:
305
+ header = []
306
+
267
307
 
268
- return table_data, [], []
308
+ return table_data, header, []
269
309
 
270
310
  if __name__ == '__main__':
271
311
  parser = argparse.ArgumentParser(description='Convert table to list of lists.')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: listpick
3
- Version: 0.1.14.5
3
+ Version: 0.1.14.7
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
@@ -1,12 +1,13 @@
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=Qmrrfx26B9Snew3j277vPlaOc4b2bjGJ2nveuk8STbE,186680
3
+ listpick/listpick_app.py,sha256=ZSfPfFbztHdjRiZVTiUi0XEKgCLWsupLrGmCPi2T9-I,188702
4
4
  listpick/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  listpick/ui/build_help.py,sha256=8QtsRosIE2IMagRc_remzmwSWpCurFgLenLL7w1ly94,8949
6
- listpick/ui/footer.py,sha256=eaGCJlUMuOyoVfBxbqDFBxF7b3w_FMUuzFO1_xWz7xY,14702
6
+ listpick/ui/footer.py,sha256=ZM5OWCxOZqy5RG6tFTe1ipvu9PRu6Gh3JCA8KXBR_Wc,15004
7
7
  listpick/ui/help_screen.py,sha256=zbfGIgb-IXtATpl4_Sx7nPbsnRXZ7eiMYlCKGS9EFmw,5608
8
- listpick/ui/input_field.py,sha256=0OBhU_-IIa_sOcehocfyeVGQ93Wk-C0Waa6dgvaQpnI,31784
9
- listpick/ui/keys.py,sha256=lhbwgsCr7pIpi10OqFMFOJSTC0nP4vpkQj9GIt787UU,13178
8
+ listpick/ui/input_field.py,sha256=PrYZICoT9M4e85W755d909TItscOae2LYvYfQsWtiXE,31068
9
+ listpick/ui/keycodes.py,sha256=1UilWAwtjhs801BrP-SndP-qIRS3AQJHl6KYKrWPgus,1614
10
+ listpick/ui/keys.py,sha256=C9wG_VPhaXq_c2bREBGKhd4Tb-AKqqOgub7y4W8xQmI,13182
10
11
  listpick/ui/pane_stuff.py,sha256=7GXa4UnV_7YmBv-baRi5moN51wYcuS4p0odl5C3m0Tc,169
11
12
  listpick/ui/picker_colours.py,sha256=FLOzvkq83orrN2bL0Mw-6RugWOZyuwUjQCrUFMUnKGY,11563
12
13
  listpick/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -21,11 +22,11 @@ listpick/utils/picker_log.py,sha256=SW6GmjxpI7YrSf72fSr4O8Ux0fY_OzaSXUgTFdz6Xo4,
21
22
  listpick/utils/search_and_filter_utils.py,sha256=XxGfkyDVXO9OAKcftPat8IReMTFIuTH-jorxI4o84tg,3239
22
23
  listpick/utils/searching.py,sha256=Xk5UIqamNHL2L90z3ACB_Giqdpi9iRKoAJ6pKaqaD7Q,3093
23
24
  listpick/utils/sorting.py,sha256=WZZiVlVA3Zkcpwji3U5SNFlQ14zVEk3cZJtQirBkecQ,5329
24
- listpick/utils/table_to_list_of_lists.py,sha256=FbLJpP8v9kPQkVnCFoM1qnq-ZiioyAKF3z7DWhZJGKA,10783
25
+ listpick/utils/table_to_list_of_lists.py,sha256=XBj7eGBDF15BRME-swnoXyOfZWxXCxrXp0pzsBfcJ5g,12224
25
26
  listpick/utils/utils.py,sha256=McOl9uT3jh7l4TIWeSd8ZGjK_e7r0YZF0Gl20yI6fl0,13873
26
- listpick-0.1.14.5.dist-info/licenses/LICENSE.txt,sha256=2mP-MRHJptADDNE9VInMNg1tE-C6Qv93Z4CCQKrpg9w,1061
27
- listpick-0.1.14.5.dist-info/METADATA,sha256=buxwDKACNAU-7m58shUjUTekCQzWu3UVNgQ6MMPWtT8,8090
28
- listpick-0.1.14.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
- listpick-0.1.14.5.dist-info/entry_points.txt,sha256=-QCf_BKIkUz35Y9nkYpjZWs2Qg0KfRna2PAs5DnF6BE,43
30
- listpick-0.1.14.5.dist-info/top_level.txt,sha256=5mtsGEz86rz3qQDe0D463gGjAfSp6A3EWg4J4AGYr-Q,9
31
- listpick-0.1.14.5.dist-info/RECORD,,
27
+ listpick-0.1.14.7.dist-info/licenses/LICENSE.txt,sha256=2mP-MRHJptADDNE9VInMNg1tE-C6Qv93Z4CCQKrpg9w,1061
28
+ listpick-0.1.14.7.dist-info/METADATA,sha256=L6KW4_BETNSyFC4HvwMLyzoU-K1uZy2KSF5RhV4z-II,8090
29
+ listpick-0.1.14.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
30
+ listpick-0.1.14.7.dist-info/entry_points.txt,sha256=-QCf_BKIkUz35Y9nkYpjZWs2Qg0KfRna2PAs5DnF6BE,43
31
+ listpick-0.1.14.7.dist-info/top_level.txt,sha256=5mtsGEz86rz3qQDe0D463gGjAfSp6A3EWg4J4AGYr-Q,9
32
+ listpick-0.1.14.7.dist-info/RECORD,,