listpick 0.1.14.5__py3-none-any.whl → 0.1.14.6__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.
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,
@@ -373,43 +373,6 @@ class Picker:
373
373
  with open(os.path.expanduser(path), "r") as f:
374
374
  config = toml.load(f)
375
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
413
376
 
414
377
  def calculate_section_sizes(self):
415
378
  """
@@ -2018,10 +1981,12 @@ class Picker:
2018
1981
  self.items, self.header = tmp_items, tmp_header
2019
1982
  self.data_ready = True
2020
1983
 
2021
- def save_input_history(self, file_path: str) -> bool:
1984
+ def save_input_history(self, file_path: str, force_save: bool=True) -> bool:
2022
1985
  """ Save input field history. Returns True if successful save. """
2023
1986
  self.logger.info(f"function: save_input_history()")
2024
1987
  file_path = os.path.expanduser(file_path)
1988
+ file_path = os.path.expandvars(file_path)
1989
+ directory = os.path.dirname(file_path)
2025
1990
  history_dict = {
2026
1991
  "history_filter_and_search" : self.history_filter_and_search,
2027
1992
  "history_pipes" : self.history_pipes,
@@ -2029,8 +1994,10 @@ class Picker:
2029
1994
  "history_edits" : self.history_edits,
2030
1995
  "history_settings": self.history_settings,
2031
1996
  }
2032
- with open(file_path, 'w') as f:
2033
- json.dump(history_dict, f)
1997
+ if os.path.exists(directory) or force_save:
1998
+ os.makedirs(os.path.dirname(file_path), exist_ok=True)
1999
+ with open(file_path, 'w') as f:
2000
+ json.dump(history_dict, f)
2034
2001
 
2035
2002
  return True
2036
2003
 
@@ -2128,27 +2095,32 @@ class Picker:
2128
2095
  self.notification(self.stdscr, message = f"File not found: {filename}")
2129
2096
  return None
2130
2097
 
2131
- filetype = guess_file_type(filename)
2132
- items, header, sheets = table_to_list(filename, file_type=filetype)
2098
+ try:
2099
+ filetype = guess_file_type(filename)
2100
+ items, header, sheets = table_to_list(filename, file_type=filetype)
2133
2101
 
2134
- if items != None:
2135
- self.items = items
2136
- self.header = header if header != None else []
2137
- self.sheets = sheets
2102
+ if items != None:
2103
+ self.items = items
2104
+ self.header = header if header != None else []
2105
+ self.sheets = sheets
2138
2106
 
2139
2107
 
2140
- self.initialise_variables()
2108
+ self.initialise_variables()
2109
+ except Exception as e:
2110
+ self.notification(self.stdscr, message=f"Error loading {filename}: {e}")
2141
2111
 
2142
2112
  def load_sheet(self, filename: str, sheet_number: int = 0):
2143
2113
  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
2114
+ try:
2115
+ items, header, sheets = table_to_list(filename, file_type=filetype, sheet_number=sheet_number)
2116
+ if items != None:
2117
+ self.items = items
2118
+ self.header = header if header != None else []
2119
+ self.sheets = sheets
2150
2120
 
2151
- self.initialise_variables()
2121
+ self.initialise_variables()
2122
+ except Exception as e:
2123
+ self.notification(self.stdscr, message=f"Error loading {filename}, sheet {sheet_number}: {e}")
2152
2124
 
2153
2125
  def switch_file(self, increment=1) -> None:
2154
2126
  """ Go to the next file. """
@@ -2161,6 +2133,7 @@ class Picker:
2161
2133
  self.loaded_file_index = (self.loaded_file_index + increment) % len(self.loaded_files)
2162
2134
  self.loaded_file = self.loaded_files[self.loaded_file_index]
2163
2135
 
2136
+ idx, file = self.loaded_file_index, self.loaded_file
2164
2137
  # If we already have a loaded state for this file
2165
2138
  if self.loaded_file_states[self.loaded_file_index]:
2166
2139
  self.set_function_data(self.loaded_file_states[self.loaded_file_index])
@@ -2168,6 +2141,7 @@ class Picker:
2168
2141
  self.set_function_data({}, reset_absent_variables=True)
2169
2142
  self.load_file(self.loaded_file)
2170
2143
 
2144
+ self.loaded_file_index, self.loaded_file = idx, file
2171
2145
 
2172
2146
  def switch_sheet(self, increment=1) -> None:
2173
2147
  if not os.path.exists(self.loaded_file):
@@ -2451,6 +2425,7 @@ class Picker:
2451
2425
  del self.loaded_file_states[self.loaded_file_index]
2452
2426
  self.loaded_file_index = min(self.loaded_file_index, len(self.loaded_files)-1)
2453
2427
  self.loaded_file = self.loaded_files[self.loaded_file_index]
2428
+ idx, file = self.loaded_file_index, self.loaded_file
2454
2429
 
2455
2430
 
2456
2431
  # If we already have a loaded state for this file
@@ -2459,6 +2434,7 @@ class Picker:
2459
2434
  else:
2460
2435
  self.set_function_data({}, reset_absent_variables=True)
2461
2436
  self.load_file(self.loaded_file)
2437
+ self.loaded_file_index, self.loaded_file = idx, file
2462
2438
  self.draw_screen(self.indexed_items, self.highlights)
2463
2439
 
2464
2440
  elif self.check_key("full_exit", key, self.keys_dict):
@@ -3467,10 +3443,11 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
3467
3443
  parser.add_argument('--stdin', dest='stdin', action='store_true', help='Table passed on stdin')
3468
3444
  parser.add_argument('--stdin2', action='store_true', help='Table passed on stdin')
3469
3445
  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)')
3446
+ parser.add_argument('--delimiter', '-d', dest='delimiter', default='\t', help='Delimiter for rows in the table (default: tab)')
3471
3447
  parser.add_argument('-t', dest='file_type', choices=['tsv', 'csv', 'json', 'xlsx', 'ods', 'pkl'], help='Type of file (tsv, csv, json, xlsx, ods)')
3472
3448
  parser.add_argument('--debug', action="store_true", help="Enable debug log.")
3473
3449
  parser.add_argument('--debug-verbose', action="store_true", help="Enable debug verbose log.")
3450
+ 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
3451
  args = parser.parse_args()
3475
3452
 
3476
3453
 
@@ -3523,13 +3500,31 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
3523
3500
  filetype = args.file_type
3524
3501
 
3525
3502
 
3526
- items, header, sheets = table_to_list(input_arg, args.delimiter, filetype)
3503
+ while True:
3504
+ try:
3505
+ items, header, sheets = table_to_list(
3506
+ input_arg=input_arg,
3507
+ delimiter=args.delimiter,
3508
+ file_type = filetype,
3509
+ first_row_is_header=args.headerless,
3510
+ )
3511
+ if args.file:
3512
+ function_data["loaded_file"] = args.file[0]
3513
+ function_data["loaded_files"] = args.file
3514
+ break
3515
+
3516
+ except Exception as e:
3517
+ items, header, sheets = [], [], []
3518
+ function_data["startup_notification"] = f"Error loading {input_arg}. {e}"
3519
+ if args.file:
3520
+ args.file = args.file[1:]
3521
+ input_arg = args.file[0]
3522
+ else:
3523
+ break
3524
+
3527
3525
  function_data["items"] = items
3528
3526
  if header: function_data["header"] = header
3529
3527
  function_data["sheets"] = sheets
3530
- if args.file:
3531
- function_data["loaded_file"] = args.file[0]
3532
- function_data["loaded_files"] = args.file
3533
3528
 
3534
3529
  return args, function_data
3535
3530
 
@@ -3577,13 +3572,41 @@ def open_tty():
3577
3572
  tty.setraw(tty_fd)
3578
3573
  return tty_fd
3579
3574
 
3580
- def get_char(tty_fd, timeout: float = 0.2) -> int:
3575
+ def get_char(tty_fd, timeout: float = 0.2, secondary: bool = False) -> int:
3581
3576
  """ Get character from a tty_fd with a timeout. """
3582
3577
  rlist, _, _ = select.select([tty_fd], [], [], timeout)
3583
3578
  if rlist:
3584
3579
  # key = ord(tty_fd.read(1))
3585
3580
  key = ord(os.read(tty_fd, 1))
3586
- # os.system(f"notify-send { key }")
3581
+ if not secondary:
3582
+ if key == 27:
3583
+ key2 = get_char(tty_fd, timeout=0.01, secondary=True)
3584
+ key3 = get_char(tty_fd, timeout=0.01, secondary=True)
3585
+ key4 = get_char(tty_fd, timeout=0.01, secondary=True)
3586
+ key5 = get_char(tty_fd, timeout=0.01, secondary=True)
3587
+ if key2 == ord('O') and key3 == ord('B'):
3588
+ key = curses.KEY_DOWN
3589
+ elif key2 == ord('O') and key3 == ord('A'):
3590
+ key = curses.KEY_UP
3591
+ elif key2 == ord('O') and key3 == ord('D'):
3592
+ key = curses.KEY_LEFT
3593
+ elif key2 == ord('O') and key3 == ord('C'):
3594
+ key = curses.KEY_RIGHT
3595
+ elif key2 == ord('[') and key3 == ord('Z'):
3596
+ key = 353
3597
+ elif key2 == ord('O') and key3 == ord('F'):
3598
+ key = curses.KEY_END
3599
+ elif key2 == ord('O') and key3 == ord('H'):
3600
+ key = curses.KEY_HOME
3601
+ elif key2 == ord('[') and key3 == ord('3') and key4 == ord('~'):
3602
+ key = curses.KEY_DC
3603
+ elif key2 == ord('[') and key3 == ord('3') and key4 == ord('~'):
3604
+ key = curses.KEY_DC
3605
+ elif key2 == ord('O') and key3 == ord('P'):
3606
+ key = curses.KEY_F1
3607
+ elif key2 == ord('[') and key3 == ord('1') and key4 == ord('5') and key5 == ord('~'):
3608
+ key = curses.KEY_F5
3609
+
3587
3610
  else:
3588
3611
  key = -1
3589
3612
  return key
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.6
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=sEe8IPPcICEZGw9Hz6mrdvkJMrIU1YxhQ-5bONvNcSc,188471
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.6.dist-info/licenses/LICENSE.txt,sha256=2mP-MRHJptADDNE9VInMNg1tE-C6Qv93Z4CCQKrpg9w,1061
28
+ listpick-0.1.14.6.dist-info/METADATA,sha256=GJpvywbTsGBGaYZVeD2mIMqQvbCF4bvA_E1Rg_4fM8Q,8090
29
+ listpick-0.1.14.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
30
+ listpick-0.1.14.6.dist-info/entry_points.txt,sha256=-QCf_BKIkUz35Y9nkYpjZWs2Qg0KfRna2PAs5DnF6BE,43
31
+ listpick-0.1.14.6.dist-info/top_level.txt,sha256=5mtsGEz86rz3qQDe0D463gGjAfSp6A3EWg4J4AGYr-Q,9
32
+ listpick-0.1.14.6.dist-info/RECORD,,