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 +85 -62
- listpick/ui/footer.py +10 -2
- listpick/ui/input_field.py +1 -19
- listpick/ui/keycodes.py +70 -0
- listpick/ui/keys.py +1 -1
- listpick/utils/table_to_list_of_lists.py +61 -21
- {listpick-0.1.14.5.dist-info → listpick-0.1.14.6.dist-info}/METADATA +1 -1
- {listpick-0.1.14.5.dist-info → listpick-0.1.14.6.dist-info}/RECORD +12 -11
- {listpick-0.1.14.5.dist-info → listpick-0.1.14.6.dist-info}/WHEEL +0 -0
- {listpick-0.1.14.5.dist-info → listpick-0.1.14.6.dist-info}/entry_points.txt +0 -0
- {listpick-0.1.14.5.dist-info → listpick-0.1.14.6.dist-info}/licenses/LICENSE.txt +0 -0
- {listpick-0.1.14.5.dist-info → listpick-0.1.14.6.dist-info}/top_level.txt +0 -0
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 =
|
|
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
|
-
|
|
2033
|
-
|
|
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
|
-
|
|
2132
|
-
|
|
2098
|
+
try:
|
|
2099
|
+
filetype = guess_file_type(filename)
|
|
2100
|
+
items, header, sheets = table_to_list(filename, file_type=filetype)
|
|
2133
2101
|
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
listpick/ui/input_field.py
CHANGED
|
@@ -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)
|
listpick/ui/keycodes.py
ADDED
|
@@ -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
|
-
|
|
60
|
-
|
|
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) >
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,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=
|
|
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=
|
|
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=
|
|
9
|
-
listpick/ui/
|
|
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=
|
|
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.
|
|
27
|
-
listpick-0.1.14.
|
|
28
|
-
listpick-0.1.14.
|
|
29
|
-
listpick-0.1.14.
|
|
30
|
-
listpick-0.1.14.
|
|
31
|
-
listpick-0.1.14.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|