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 +101 -69
- 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.7.dist-info}/METADATA +1 -1
- {listpick-0.1.14.5.dist-info → listpick-0.1.14.7.dist-info}/RECORD +12 -11
- {listpick-0.1.14.5.dist-info → listpick-0.1.14.7.dist-info}/WHEEL +0 -0
- {listpick-0.1.14.5.dist-info → listpick-0.1.14.7.dist-info}/entry_points.txt +0 -0
- {listpick-0.1.14.5.dist-info → listpick-0.1.14.7.dist-info}/licenses/LICENSE.txt +0 -0
- {listpick-0.1.14.5.dist-info → listpick-0.1.14.7.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,
|
|
@@ -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
|
-
|
|
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
|
-
|
|
373
|
-
|
|
374
|
-
|
|
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
|
-
|
|
2033
|
-
|
|
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
|
-
|
|
2132
|
-
|
|
2107
|
+
try:
|
|
2108
|
+
filetype = guess_file_type(filename)
|
|
2109
|
+
items, header, sheets = table_to_list(filename, file_type=filetype)
|
|
2133
2110
|
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
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=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=
|
|
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.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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|