listpick 0.1.16.0__py3-none-any.whl → 0.1.16.3__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 +60 -19
- listpick/ui/footer.py +3 -3
- listpick/utils/generate_data.py +17 -0
- listpick/utils/generate_data_multithreaded.py +202 -0
- {listpick-0.1.16.0.dist-info → listpick-0.1.16.3.dist-info}/METADATA +5 -5
- {listpick-0.1.16.0.dist-info → listpick-0.1.16.3.dist-info}/RECORD +10 -9
- {listpick-0.1.16.0.dist-info → listpick-0.1.16.3.dist-info}/WHEEL +0 -0
- {listpick-0.1.16.0.dist-info → listpick-0.1.16.3.dist-info}/entry_points.txt +0 -0
- {listpick-0.1.16.0.dist-info → listpick-0.1.16.3.dist-info}/licenses/LICENSE.txt +0 -0
- {listpick-0.1.16.0.dist-info → listpick-0.1.16.3.dist-info}/top_level.txt +0 -0
listpick/listpick_app.py
CHANGED
|
@@ -36,7 +36,8 @@ from listpick.utils.paste_operations import *
|
|
|
36
36
|
from listpick.utils.searching import search
|
|
37
37
|
from listpick.ui.help_screen import help_lines
|
|
38
38
|
from listpick.ui.keys import picker_keys, notification_keys, options_keys, help_keys
|
|
39
|
-
from listpick.utils.generate_data import generate_picker_data
|
|
39
|
+
# from listpick.utils.generate_data import generate_picker_data
|
|
40
|
+
from listpick.utils.generate_data_multithreaded import generate_picker_data_from_file
|
|
40
41
|
from listpick.utils.dump import dump_state, load_state, dump_data
|
|
41
42
|
from listpick.ui.build_help import build_help_rows
|
|
42
43
|
from listpick.ui.footer import StandardFooter, CompactFooter, NoFooter
|
|
@@ -77,7 +78,7 @@ class Picker:
|
|
|
77
78
|
timer: float = 5,
|
|
78
79
|
|
|
79
80
|
get_new_data: bool =False, # Whether we can get new data
|
|
80
|
-
refresh_function: Optional[Callable] = lambda:
|
|
81
|
+
refresh_function: Optional[Callable] = lambda items, header, visible_rows_indices, getting_data: None, # The function with which we get new data
|
|
81
82
|
get_data_startup: bool =False, # Whether we should get data at statrup
|
|
82
83
|
track_entries_upon_refresh: bool = True,
|
|
83
84
|
pin_cursor: bool = False,
|
|
@@ -146,7 +147,7 @@ class Picker:
|
|
|
146
147
|
footer_style: int = 0,
|
|
147
148
|
footer_string: str="",
|
|
148
149
|
footer_string_auto_refresh: bool=False,
|
|
149
|
-
footer_string_refresh_function: Optional[Callable] = None,
|
|
150
|
+
footer_string_refresh_function: Optional[Callable] = lambda : None,
|
|
150
151
|
footer_timer: float=1,
|
|
151
152
|
get_footer_string_startup=False,
|
|
152
153
|
unicode_char_width: bool = True,
|
|
@@ -197,6 +198,8 @@ class Picker:
|
|
|
197
198
|
right_panes: list = [],
|
|
198
199
|
right_pane_index: int = 0,
|
|
199
200
|
|
|
201
|
+
# getting_data: threading.Event = threading.Event(),
|
|
202
|
+
|
|
200
203
|
):
|
|
201
204
|
self.stdscr = stdscr
|
|
202
205
|
self.items = items
|
|
@@ -282,7 +285,7 @@ class Picker:
|
|
|
282
285
|
self.footer_string_auto_refresh = footer_string_auto_refresh
|
|
283
286
|
self.footer_string_refresh_function = footer_string_refresh_function
|
|
284
287
|
self.footer_timer = footer_timer
|
|
285
|
-
self.get_footer_string_startup = get_footer_string_startup
|
|
288
|
+
self.get_footer_string_startup = get_footer_string_startup
|
|
286
289
|
self.unicode_char_width = unicode_char_width
|
|
287
290
|
|
|
288
291
|
|
|
@@ -314,7 +317,6 @@ class Picker:
|
|
|
314
317
|
|
|
315
318
|
|
|
316
319
|
# Refresh function variables
|
|
317
|
-
self.data_refreshed = False
|
|
318
320
|
self.refreshing_data = False
|
|
319
321
|
self.data_lock = threading.Lock()
|
|
320
322
|
self.data_ready = False
|
|
@@ -349,12 +351,22 @@ class Picker:
|
|
|
349
351
|
self.split_right = split_right
|
|
350
352
|
self.right_panes = right_panes
|
|
351
353
|
self.right_pane_index = right_pane_index
|
|
354
|
+
self.visible_rows_indices = []
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
|
|
352
359
|
self.initialise_picker_state(reset_colours=self.reset_colours)
|
|
353
360
|
|
|
354
361
|
# Note: We have to set the footer after initialising the picker state so that the footer can use the get_function_data method
|
|
355
362
|
self.footer_options = [StandardFooter(self.stdscr, colours_start, self.get_function_data), CompactFooter(self.stdscr, colours_start, self.get_function_data), NoFooter(self.stdscr, colours_start, self.get_function_data)]
|
|
356
363
|
self.footer = self.footer_options[self.footer_style]
|
|
357
|
-
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
# getting_data.is_set() is True when we are getting data
|
|
368
|
+
self.getting_data = threading.Event()
|
|
369
|
+
self.getting_data.set()
|
|
358
370
|
|
|
359
371
|
def __sizeof__(self):
|
|
360
372
|
|
|
@@ -463,8 +475,11 @@ class Picker:
|
|
|
463
475
|
end_index = min(start_index + self.items_per_page, len(self.indexed_items))
|
|
464
476
|
if len(self.indexed_items) == 0: start_index, end_index = 0, 0
|
|
465
477
|
|
|
466
|
-
|
|
467
|
-
|
|
478
|
+
self.visible_rows = [v[1] for v in self.indexed_items[start_index:end_index]] if len(self.indexed_items) else self.items
|
|
479
|
+
# self.visible_rows_indices = [v[0] for v in self.indexed_items[start_index:end_index]] if len(self.indexed_items) else []
|
|
480
|
+
self.visible_rows_indices.clear()
|
|
481
|
+
self.visible_rows_indices.extend([v[0] for v in self.indexed_items[start_index:end_index]])
|
|
482
|
+
return self.visible_rows
|
|
468
483
|
|
|
469
484
|
def initialise_picker_state(self, reset_colours=False) -> None:
|
|
470
485
|
""" Initialise state variables for the picker. These are: debugging and colours. """
|
|
@@ -558,7 +573,10 @@ class Picker:
|
|
|
558
573
|
self.cursor_pos_id = self.indexed_items[self.cursor_pos][1][self.id_column]
|
|
559
574
|
self.cursor_pos_prev = self.cursor_pos
|
|
560
575
|
|
|
561
|
-
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
self.getting_data.clear()
|
|
579
|
+
self.refresh_function(self.items, self.header, self.visible_rows_indices, self.getting_data)
|
|
562
580
|
|
|
563
581
|
self.items = pad_lists_to_same_length(self.items)
|
|
564
582
|
|
|
@@ -566,7 +584,7 @@ class Picker:
|
|
|
566
584
|
## Ensure that items is a List[List[Str]] object
|
|
567
585
|
if len(self.items) > 0 and not isinstance(self.items[0], list):
|
|
568
586
|
self.items = [[item] for item in self.items]
|
|
569
|
-
self.items = [[str(cell) for cell in row] for row in self.items]
|
|
587
|
+
# self.items = [[str(cell) for cell in row] for row in self.items]
|
|
570
588
|
|
|
571
589
|
|
|
572
590
|
# Ensure that header is of the same length as the rows
|
|
@@ -820,8 +838,9 @@ class Picker:
|
|
|
820
838
|
# Determine widths based only on the currently indexed rows
|
|
821
839
|
# rows = [v[1] for v in self.indexed_items] if len(self.indexed_items) else self.items
|
|
822
840
|
# Determine widths based only on the currently displayed indexed rows
|
|
823
|
-
rows = [v[1] for v in self.indexed_items[start_index:end_index]] if len(self.indexed_items) else self.items
|
|
824
|
-
self.
|
|
841
|
+
# rows = [v[1] for v in self.indexed_items[start_index:end_index]] if len(self.indexed_items) else self.items
|
|
842
|
+
self.get_visible_rows()
|
|
843
|
+
self.column_widths = get_column_widths(self.visible_rows, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns, max_total_width=self.rows_w, unicode_char_width=self.unicode_char_width)
|
|
825
844
|
visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
|
|
826
845
|
visible_columns_total_width = sum(visible_column_widths) + len(self.separator)*(len(visible_column_widths)-1)
|
|
827
846
|
|
|
@@ -1199,6 +1218,11 @@ class Picker:
|
|
|
1199
1218
|
else:
|
|
1200
1219
|
self.stdscr.addstr(0,self.term_w-3," ", curses.color_pair(self.colours_start+23) | curses.A_BOLD)
|
|
1201
1220
|
|
|
1221
|
+
# Display data fetch symbol
|
|
1222
|
+
if not self.getting_data.is_set():
|
|
1223
|
+
self.stdscr.addstr(0,self.term_w-3," ", curses.color_pair(self.colours_start+21) | curses.A_BOLD)
|
|
1224
|
+
# self.stdscr.addstr(0,self.term_w-6,"⏳", curses.color_pair(self.colours_start+21) | curses.A_BOLD)
|
|
1225
|
+
|
|
1202
1226
|
## Display footer
|
|
1203
1227
|
if self.show_footer:
|
|
1204
1228
|
# self.footer = NoFooter(self.stdscr, self.colours_start, self.get_function_data)
|
|
@@ -1592,6 +1616,7 @@ class Picker:
|
|
|
1592
1616
|
"split_right": False,
|
|
1593
1617
|
"cell_cursor": False,
|
|
1594
1618
|
"crosshair_cursor": False,
|
|
1619
|
+
"show_header": False,
|
|
1595
1620
|
|
|
1596
1621
|
}
|
|
1597
1622
|
OptionPicker = Picker(submenu_win, **notification_data)
|
|
@@ -2123,7 +2148,7 @@ class Picker:
|
|
|
2123
2148
|
|
|
2124
2149
|
self.stdscr = tmp
|
|
2125
2150
|
|
|
2126
|
-
self.notification(self.stdscr, f"{repr(file_to_load)} has been loaded!")
|
|
2151
|
+
# self.notification(self.stdscr, f"{repr(file_to_load)} has been loaded!")
|
|
2127
2152
|
|
|
2128
2153
|
self.set_function_data({}, reset_absent_variables=True)
|
|
2129
2154
|
self.load_file(self.loaded_file)
|
|
@@ -2146,7 +2171,9 @@ class Picker:
|
|
|
2146
2171
|
def fetch_data(self) -> None:
|
|
2147
2172
|
""" Refesh data asynchronously. When data has been fetched self.data_ready is set to True. """
|
|
2148
2173
|
self.logger.info(f"function: fetch_data()")
|
|
2149
|
-
tmp_items, tmp_header =
|
|
2174
|
+
tmp_items, tmp_header = [], []
|
|
2175
|
+
self.getting_data.clear()
|
|
2176
|
+
self.refresh_function(tmp_items, tmp_header, self.visible_rows_indices, self.getting_data)
|
|
2150
2177
|
if self.track_entries_upon_refresh:
|
|
2151
2178
|
selected_indices = get_selected_indices(self.selections)
|
|
2152
2179
|
self.ids = [item[self.id_column] for i, item in enumerate(self.items) if i in selected_indices]
|
|
@@ -2424,6 +2451,10 @@ class Picker:
|
|
|
2424
2451
|
else: return False
|
|
2425
2452
|
|
|
2426
2453
|
COLS, LINES = os.get_terminal_size()
|
|
2454
|
+
|
|
2455
|
+
|
|
2456
|
+
getting_data_prev = False
|
|
2457
|
+
|
|
2427
2458
|
# Main loop
|
|
2428
2459
|
while True:
|
|
2429
2460
|
# key = self.stdscr.getch()
|
|
@@ -2432,6 +2463,16 @@ class Picker:
|
|
|
2432
2463
|
if key != -1:
|
|
2433
2464
|
self.logger.info(f"key={key}")
|
|
2434
2465
|
|
|
2466
|
+
# Ensure that
|
|
2467
|
+
|
|
2468
|
+
if not self.getting_data.is_set():
|
|
2469
|
+
self.initialise_variables()
|
|
2470
|
+
getting_data_prev = True
|
|
2471
|
+
elif getting_data_prev:
|
|
2472
|
+
## Ensure that we reinitialise one final time after all data is retrieved.
|
|
2473
|
+
self.initialise_variables()
|
|
2474
|
+
getting_data_prev = False
|
|
2475
|
+
|
|
2435
2476
|
self.term_resize_event = terminal_resized(COLS, LINES)
|
|
2436
2477
|
COLS, LINES = os.get_terminal_size()
|
|
2437
2478
|
if self.term_resize_event:
|
|
@@ -2950,8 +2991,8 @@ class Picker:
|
|
|
2950
2991
|
|
|
2951
2992
|
|
|
2952
2993
|
## Scroll with column select
|
|
2953
|
-
|
|
2954
|
-
self.column_widths = get_column_widths(
|
|
2994
|
+
self.get_visible_rows()
|
|
2995
|
+
self.column_widths = get_column_widths(self.visible_rows, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns, max_total_width=self.rows_w, unicode_char_width=self.unicode_char_width)
|
|
2955
2996
|
visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
|
|
2956
2997
|
column_set_width = sum(visible_column_widths)+len(self.separator)*len(visible_column_widths)
|
|
2957
2998
|
start_of_cell = sum(visible_column_widths[:self.selected_column])+len(self.separator)*self.selected_column
|
|
@@ -2977,8 +3018,8 @@ class Picker:
|
|
|
2977
3018
|
# curses.flash()
|
|
2978
3019
|
|
|
2979
3020
|
## Scroll with column select
|
|
2980
|
-
|
|
2981
|
-
self.column_widths = get_column_widths(
|
|
3021
|
+
self.get_visible_rows()
|
|
3022
|
+
self.column_widths = get_column_widths(self.visible_rows, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns, max_total_width=self.rows_w, unicode_char_width=self.unicode_char_width)
|
|
2982
3023
|
visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
|
|
2983
3024
|
column_set_width = sum(visible_column_widths)+len(self.separator)*len(visible_column_widths)
|
|
2984
3025
|
start_of_cell = sum(visible_column_widths[:self.selected_column])+len(self.separator)*self.selected_column
|
|
@@ -3708,7 +3749,7 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
|
|
|
3708
3749
|
# input_arg = args.filename
|
|
3709
3750
|
|
|
3710
3751
|
elif args.generate:
|
|
3711
|
-
function_data["refresh_function"] = lambda :
|
|
3752
|
+
function_data["refresh_function"] = lambda items, header, visible_rows_indices, getting_data: generate_picker_data_from_file(args.generate, items, header, visible_rows_indices, getting_data)
|
|
3712
3753
|
function_data["get_data_startup"] = True
|
|
3713
3754
|
function_data["get_new_data"] = True
|
|
3714
3755
|
return args, function_data
|
listpick/ui/footer.py
CHANGED
|
@@ -160,7 +160,7 @@ class StandardFooter(Footer):
|
|
|
160
160
|
if state["footer_string"]:
|
|
161
161
|
footer_string_width = min(w-1, len(state["footer_string"])+2)
|
|
162
162
|
|
|
163
|
-
disp_string = f"{state[
|
|
163
|
+
disp_string = f"{state['footer_string'][:footer_string_width]}"
|
|
164
164
|
disp_string = f" {disp_string:>{footer_string_width-2}} "
|
|
165
165
|
self.stdscr.addstr(self.footer_string_y, w-footer_string_width-1, " "*footer_string_width, curses.color_pair(self.colours_start+24))
|
|
166
166
|
self.stdscr.addstr(self.footer_string_y, w-footer_string_width-1, f"{disp_string}", curses.color_pair(self.colours_start+24))
|
|
@@ -259,7 +259,7 @@ class CompactFooter(Footer):
|
|
|
259
259
|
if state["footer_string"]:
|
|
260
260
|
footer_string_width = min(w-1, len(state["footer_string"])+2)
|
|
261
261
|
|
|
262
|
-
disp_string = f"{state[
|
|
262
|
+
disp_string = f"{state['footer_string'][:footer_string_width]}"
|
|
263
263
|
disp_string = f" {disp_string:>{footer_string_width-2}} "
|
|
264
264
|
self.stdscr.addstr(h - 1, w-footer_string_width-1, " "*footer_string_width, curses.color_pair(self.colours_start+24))
|
|
265
265
|
self.stdscr.addstr(h - 1, w-footer_string_width-1, f"{disp_string}", curses.color_pair(self.colours_start+24))
|
|
@@ -318,7 +318,7 @@ class NoFooter(Footer):
|
|
|
318
318
|
|
|
319
319
|
if state["footer_string"]:
|
|
320
320
|
footer_string_width = min(w-1, len(state["footer_string"])+2)
|
|
321
|
-
disp_string = f"{state[
|
|
321
|
+
disp_string = f"{state['footer_string'][:footer_string_width]}"
|
|
322
322
|
disp_string = f" {disp_string:>{footer_string_width-2}} "
|
|
323
323
|
self.stdscr.addstr(h - 1, w-footer_string_width-1, " "*footer_string_width, curses.color_pair(self.colours_start+24))
|
|
324
324
|
self.stdscr.addstr(h - 1, w-footer_string_width-1, f"{disp_string}", curses.color_pair(self.colours_start+24))
|
listpick/utils/generate_data.py
CHANGED
|
@@ -35,6 +35,23 @@ def generate_columns(funcs: list, files: list) -> list[list[str]]:
|
|
|
35
35
|
return results
|
|
36
36
|
|
|
37
37
|
|
|
38
|
+
def generate_columns_multithread(funcs: list, files: list) -> list[list[str]]:
|
|
39
|
+
"""
|
|
40
|
+
Takes a list of functions and a list of files.
|
|
41
|
+
Tasks are run in parallel using concurrent.futures.
|
|
42
|
+
"""
|
|
43
|
+
logger.info("function: generate_columns (generate_data.py)")
|
|
44
|
+
|
|
45
|
+
results = []
|
|
46
|
+
# Create a future object for each combination of func and file
|
|
47
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
48
|
+
futures = [[executor.submit(func, file) for func in funcs] for file in files]
|
|
49
|
+
|
|
50
|
+
for file_futures in futures:
|
|
51
|
+
result = [future.result() for future in file_futures]
|
|
52
|
+
results.append(result)
|
|
53
|
+
return results
|
|
54
|
+
|
|
38
55
|
def generate_columns_single_thread(funcs: list, files: list) -> list[list[str]]:
|
|
39
56
|
"""
|
|
40
57
|
Takes a list of functions and a list of files. Each function is run for each file and a list of lists is returned.
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
#!/bin/python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""
|
|
4
|
+
generate_data_multithreaded.py
|
|
5
|
+
Generate data for listpick Picker.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
1. Read toml file.
|
|
9
|
+
2. Set environment variables.
|
|
10
|
+
3. Get files from first command.
|
|
11
|
+
4. Create items with "..." for cells to be filled.
|
|
12
|
+
5. Create a priority queue which determines which cells are to be filled first.
|
|
13
|
+
6. Create a queue updater which increases the priorty of cells which are on screen which does so each second.
|
|
14
|
+
7. Create threads to start generating data for cells.
|
|
15
|
+
|
|
16
|
+
Author: GrimAndGreedy
|
|
17
|
+
License: MIT
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import subprocess
|
|
21
|
+
import os
|
|
22
|
+
from typing import Tuple, Callable
|
|
23
|
+
import toml
|
|
24
|
+
import logging
|
|
25
|
+
import threading
|
|
26
|
+
from queue import PriorityQueue
|
|
27
|
+
import time
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger('picker_log')
|
|
30
|
+
|
|
31
|
+
def generate_columns_worker(
|
|
32
|
+
funcs: list,
|
|
33
|
+
files: list,
|
|
34
|
+
items: list[list[str]],
|
|
35
|
+
getting_data: threading.Event,
|
|
36
|
+
task_queue: PriorityQueue,
|
|
37
|
+
completed_cells: set,
|
|
38
|
+
) -> None:
|
|
39
|
+
""" Get a task from the priorty queue and fill the data for that cell."""
|
|
40
|
+
while task_queue.qsize() > 0:
|
|
41
|
+
_, (i, j) = task_queue.get()
|
|
42
|
+
|
|
43
|
+
if (i, j) in completed_cells:
|
|
44
|
+
task_queue.task_done()
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
generate_cell(
|
|
48
|
+
func=funcs[j],
|
|
49
|
+
file=files[i],
|
|
50
|
+
items=items,
|
|
51
|
+
row=i,
|
|
52
|
+
col=j+1,
|
|
53
|
+
)
|
|
54
|
+
completed_cells.add((i, j))
|
|
55
|
+
task_queue.task_done()
|
|
56
|
+
getting_data.set()
|
|
57
|
+
|
|
58
|
+
def generate_cell(func: Callable, file: str, items: list[list[str]], row: int, col: int) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Takes a function, file and a file and then sets items[row][col] to the result.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
items[row][col] = func(file).strip()
|
|
64
|
+
|
|
65
|
+
def update_queue(task_queue: PriorityQueue, visible_rows_indices: list[int], rows: int, cols: int):
|
|
66
|
+
""" Increase the priority of getting the data for the cells that are currently visible. """
|
|
67
|
+
while task_queue.qsize() > 0:
|
|
68
|
+
time.sleep(0.1)
|
|
69
|
+
for row in visible_rows_indices:
|
|
70
|
+
for col in range(cols):
|
|
71
|
+
if 0 <= row < rows:
|
|
72
|
+
task_queue.put((1, (row, col)))
|
|
73
|
+
|
|
74
|
+
# Delay
|
|
75
|
+
time.sleep(0.9)
|
|
76
|
+
|
|
77
|
+
def command_to_func(command: str) -> Callable:
|
|
78
|
+
"""
|
|
79
|
+
Convert a command string to a function that will run the command.
|
|
80
|
+
|
|
81
|
+
E.g.,
|
|
82
|
+
mediainfo {} | grep -i format
|
|
83
|
+
mediainfo {} | grep -i format | head -n 1 | awk '{{print $3}}'
|
|
84
|
+
"""
|
|
85
|
+
logger.info("function: command_to_func (generate_data.py)")
|
|
86
|
+
|
|
87
|
+
func = lambda arg: subprocess.run(command.format(repr(arg)), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.decode("utf-8").strip()
|
|
88
|
+
return func
|
|
89
|
+
|
|
90
|
+
def load_environment(envs:dict):
|
|
91
|
+
"""
|
|
92
|
+
Load environment variables from an envs dict.
|
|
93
|
+
"""
|
|
94
|
+
logger.info("function: load_environment (generate_data.py)")
|
|
95
|
+
|
|
96
|
+
if "cwd" in envs:
|
|
97
|
+
os.chdir(os.path.expandvars(os.path.expanduser(envs["cwd"])))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def read_toml(file_path) -> Tuple[dict, list, list]:
|
|
101
|
+
"""
|
|
102
|
+
Read toml file and return the environment, commands and header sections.
|
|
103
|
+
"""
|
|
104
|
+
logger.info("function: read_toml (generate_data.py)")
|
|
105
|
+
with open(file_path, 'r') as file:
|
|
106
|
+
config = toml.load(file)
|
|
107
|
+
|
|
108
|
+
environment = config['environment'] if 'environment' in config else {}
|
|
109
|
+
data = config['data'] if 'data' in config else {}
|
|
110
|
+
commands = [command.strip() for command in data['commands']] if 'commands' in data else []
|
|
111
|
+
header = [header for header in data['header']] if 'header' in data else []
|
|
112
|
+
return environment, commands, header
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def generate_picker_data_from_file(
|
|
116
|
+
file_path: str,
|
|
117
|
+
items,
|
|
118
|
+
header,
|
|
119
|
+
visible_rows_indices,
|
|
120
|
+
getting_data
|
|
121
|
+
) -> None:
|
|
122
|
+
"""
|
|
123
|
+
Generate data for Picker based upon the toml file commands.
|
|
124
|
+
"""
|
|
125
|
+
logger.info("function: generate_picker_data (generate_data.py)")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
environment, lines, hdr = read_toml(file_path)
|
|
129
|
+
|
|
130
|
+
# Load any environment variables from the toml file
|
|
131
|
+
if environment:
|
|
132
|
+
load_environment(environment)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
# Get list of files to be displayed in the first column.
|
|
136
|
+
get_files_command = lines[0]
|
|
137
|
+
files = subprocess.run(get_files_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).stdout.decode("utf-8").strip().split("\n")
|
|
138
|
+
files = [file.strip() for file in files if files]
|
|
139
|
+
|
|
140
|
+
commands_list = [line.strip() for line in lines[1:]]
|
|
141
|
+
command_funcs = [command_to_func(command) for command in commands_list]
|
|
142
|
+
|
|
143
|
+
generate_picker_data(
|
|
144
|
+
files = files,
|
|
145
|
+
column_functions = command_funcs,
|
|
146
|
+
data_header = hdr,
|
|
147
|
+
items = items,
|
|
148
|
+
picker_header = header,
|
|
149
|
+
visible_rows_indices = visible_rows_indices,
|
|
150
|
+
getting_data = getting_data,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def generate_picker_data(
|
|
154
|
+
files: list[str],
|
|
155
|
+
column_functions: list[Callable],
|
|
156
|
+
data_header,
|
|
157
|
+
items,
|
|
158
|
+
picker_header,
|
|
159
|
+
visible_rows_indices,
|
|
160
|
+
getting_data
|
|
161
|
+
) -> None:
|
|
162
|
+
"""
|
|
163
|
+
Generate data from a list of files and a list of column functions which will be used to
|
|
164
|
+
generate subsequent columns.
|
|
165
|
+
|
|
166
|
+
This function is performed asynchronously with os.cpu_count() threads.
|
|
167
|
+
|
|
168
|
+
data_header: header list to be set for the picker
|
|
169
|
+
picker_header: the picker header will be passed in so that it can be set for the class
|
|
170
|
+
|
|
171
|
+
"""
|
|
172
|
+
logger.info("function: generate_picker_data (generate_data.py)")
|
|
173
|
+
|
|
174
|
+
items.clear()
|
|
175
|
+
items.extend([[file] + ["..." for _ in column_functions] for file in files])
|
|
176
|
+
# items[:] = [[file] + ["..." for _ in column_functions] for file in files]
|
|
177
|
+
picker_header[:] = data_header
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
task_queue = PriorityQueue()
|
|
181
|
+
for i in range(len(files)):
|
|
182
|
+
for j in range(len(column_functions)):
|
|
183
|
+
task_queue.put((10, (i, j)))
|
|
184
|
+
|
|
185
|
+
num_workers = os.cpu_count()
|
|
186
|
+
if num_workers in [None, -1]: num_workers = 4
|
|
187
|
+
completed_cells = set()
|
|
188
|
+
|
|
189
|
+
for _ in range(num_workers):
|
|
190
|
+
gen_items_thread = threading.Thread(
|
|
191
|
+
target=generate_columns_worker,
|
|
192
|
+
args=(column_functions, files, items, getting_data, task_queue, completed_cells),
|
|
193
|
+
)
|
|
194
|
+
gen_items_thread.daemon = True
|
|
195
|
+
gen_items_thread.start()
|
|
196
|
+
|
|
197
|
+
update_queue_thread = threading.Thread(
|
|
198
|
+
target=update_queue,
|
|
199
|
+
args=(task_queue, visible_rows_indices, len(files), len(column_functions)),
|
|
200
|
+
)
|
|
201
|
+
update_queue_thread.daemon = True
|
|
202
|
+
update_queue_thread.start()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: listpick
|
|
3
|
-
Version: 0.1.16.
|
|
3
|
+
Version: 0.1.16.3
|
|
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
|
|
@@ -45,7 +45,7 @@ Dynamic: summary
|
|
|
45
45
|
|
|
46
46
|
listpick is a TUI tool which displays a tabulated list of rows and allows the user to operate upon these rows--select, copy, pipe. A very simple concept but also, I hope, a powerful tool that will make it easier for people to develop TUI apps.
|
|
47
47
|
|
|
48
|
-
Rows of data can be viewed, selected, generated, saved, loaded, refreshed, modified or copied to the clipboard. Easy to integrate into your project by creating a `menu = Picker(stdscr, items:list[list[str]])` and then the menu will be displayed by running `menu.run()`.
|
|
48
|
+
Rows of data can be viewed, selected, generated, saved, loaded, refreshed, modified or copied to the clipboard. Easy to integrate into your project by creating a `menu = Picker(stdscr: curses.window, items: list[list[str]])` and then the menu will be displayed by running `menu.run()`.
|
|
49
49
|
|
|
50
50
|
It works great as the backend for a TUI application and can also be used as a standalone data viewer.
|
|
51
51
|
|
|
@@ -53,13 +53,13 @@ It works great as the backend for a TUI application and can also be used as a st
|
|
|
53
53
|
|
|
54
54
|
# Quickstart
|
|
55
55
|
|
|
56
|
-
Install listpick
|
|
56
|
+
Install listpick:
|
|
57
57
|
|
|
58
58
|
```
|
|
59
59
|
python -m pip installl "listpick[full]"
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
Create a
|
|
62
|
+
Create a Picker:
|
|
63
63
|
|
|
64
64
|
```
|
|
65
65
|
from listpick.listpick_app import Picker, start_curses, close_curses
|
|
@@ -79,7 +79,7 @@ x.run()
|
|
|
79
79
|
close_curses(stdscr)
|
|
80
80
|
|
|
81
81
|
```
|
|
82
|
-
|
|
82
|
+
Or use the listpick binary to generate and display rows based on a list of commands:
|
|
83
83
|
|
|
84
84
|
```
|
|
85
85
|
wget https://raw.githubusercontent.com/grimandgreedy/listpick/refs/heads/master/examples/data_generation/list_files.toml
|
|
@@ -1,13 +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=MDErM54fA_qvgMbo-IGovnUoDliBbaMSEGbHno-zhHA,203676
|
|
4
4
|
listpick/pane/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
listpick/pane/get_data.py,sha256=l12mHIb6qoZWIfW5zZZY8K8EqNcyIcRiHgtRaM2CVGs,2735
|
|
6
6
|
listpick/pane/pane_functions.py,sha256=_dL9jHpd3sT0enL9H_bMcUsBlMELXdtP9dtKFSC2KPQ,5117
|
|
7
7
|
listpick/pane/pane_utils.py,sha256=OEL4yBnBlIiHnT5bYtb3nQMn3WUR0vVH0gAGCtL1t0o,2539
|
|
8
8
|
listpick/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
9
|
listpick/ui/build_help.py,sha256=IpN4tX4U8ET3icRoTlF389VqVjVcPAon617vgstlPAc,10750
|
|
10
|
-
listpick/ui/footer.py,sha256=
|
|
10
|
+
listpick/ui/footer.py,sha256=NcdH1uO_ma91m0qCczyQZ3zGrexfkiEnwDf5E4tHSMk,15089
|
|
11
11
|
listpick/ui/help_screen.py,sha256=zbfGIgb-IXtATpl4_Sx7nPbsnRXZ7eiMYlCKGS9EFmw,5608
|
|
12
12
|
listpick/ui/input_field.py,sha256=ylf3fiLXdAD4pueHWfzIrlwaRb9f5zm8f1UGkEPBxgM,30539
|
|
13
13
|
listpick/ui/keys.py,sha256=1NCsOPFxhNuVPbO5vflcArNZ1jbBBerPQ3oZUAZDyPs,13682
|
|
@@ -17,7 +17,8 @@ listpick/utils/clipboard_operations.py,sha256=ORdNm2kgGbfs51xJSvgJPERgoSmBgT11ax
|
|
|
17
17
|
listpick/utils/config.py,sha256=MEnAZg2Rhfl38XofEIN0uoVAOY7I0ftc79Evk3fOiVw,1654
|
|
18
18
|
listpick/utils/dump.py,sha256=60YVIMNtBoYvWhmzfTJOsNGcetOvcCB3_T7yv-bYTPQ,3838
|
|
19
19
|
listpick/utils/filtering.py,sha256=59_YIEYRV0ovnjF4iyuShq276FMAx5gBD9m3mE9IqJg,1237
|
|
20
|
-
listpick/utils/generate_data.py,sha256=
|
|
20
|
+
listpick/utils/generate_data.py,sha256=YxkAPDkQnkeBmxQq8K7n6so-OUKC8DCPZqoqixYO2jw,4438
|
|
21
|
+
listpick/utils/generate_data_multithreaded.py,sha256=AvAeejmDujwTAueLnl9giC2NKxhrOmk1IILVQgE5M-g,6305
|
|
21
22
|
listpick/utils/graphing.py,sha256=ugjAH8js_iH7hulg4SySxb_W_f8B6GhTaceN5i7DID4,6954
|
|
22
23
|
listpick/utils/keycodes.py,sha256=ZGkw1-4szxPnP81wj80r92L6_neIOlBBjQltEieCwnk,2696
|
|
23
24
|
listpick/utils/options_selectors.py,sha256=fQqTCAqRsrMhZwXUtsPBm47svkaUaeJqWsMW7Q4JTYY,3083
|
|
@@ -29,9 +30,9 @@ listpick/utils/sorting.py,sha256=WZZiVlVA3Zkcpwji3U5SNFlQ14zVEk3cZJtQirBkecQ,532
|
|
|
29
30
|
listpick/utils/table_to_list_of_lists.py,sha256=XBj7eGBDF15BRME-swnoXyOfZWxXCxrXp0pzsBfcJ5g,12224
|
|
30
31
|
listpick/utils/user_input.py,sha256=oyJZPAwA7UGAclPhdPL44tKnPIVNHWhX-tZEnCdBKC0,4318
|
|
31
32
|
listpick/utils/utils.py,sha256=QptLnqT15Z3G7xoq-6Q8e4-aZRFClTdb3h0l42di11s,13810
|
|
32
|
-
listpick-0.1.16.
|
|
33
|
-
listpick-0.1.16.
|
|
34
|
-
listpick-0.1.16.
|
|
35
|
-
listpick-0.1.16.
|
|
36
|
-
listpick-0.1.16.
|
|
37
|
-
listpick-0.1.16.
|
|
33
|
+
listpick-0.1.16.3.dist-info/licenses/LICENSE.txt,sha256=2mP-MRHJptADDNE9VInMNg1tE-C6Qv93Z4CCQKrpg9w,1061
|
|
34
|
+
listpick-0.1.16.3.dist-info/METADATA,sha256=8HD-1Fi-J4ZjWaMRNveU7xUjzepKEUXsXAB_mpl7RQI,8128
|
|
35
|
+
listpick-0.1.16.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
36
|
+
listpick-0.1.16.3.dist-info/entry_points.txt,sha256=-QCf_BKIkUz35Y9nkYpjZWs2Qg0KfRna2PAs5DnF6BE,43
|
|
37
|
+
listpick-0.1.16.3.dist-info/top_level.txt,sha256=5mtsGEz86rz3qQDe0D463gGjAfSp6A3EWg4J4AGYr-Q,9
|
|
38
|
+
listpick-0.1.16.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|