listpick 0.1.16.0__tar.gz → 0.1.16.1__tar.gz
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-0.1.16.0 → listpick-0.1.16.1}/CHANGELOG.md +16 -3
- {listpick-0.1.16.0/src/listpick.egg-info → listpick-0.1.16.1}/PKG-INFO +5 -5
- {listpick-0.1.16.0 → listpick-0.1.16.1}/README.md +4 -4
- {listpick-0.1.16.0 → listpick-0.1.16.1}/TODO.md +6 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/examples/data_generation/video_duplicates.toml +22 -11
- {listpick-0.1.16.0 → listpick-0.1.16.1}/setup.py +1 -1
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/listpick_app.py +59 -19
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/generate_data.py +17 -0
- listpick-0.1.16.1/src/listpick/utils/generate_data_multithreaded.py +202 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1/src/listpick.egg-info}/PKG-INFO +5 -5
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick.egg-info/SOURCES.txt +1 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/.gitignore +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/LICENSE.txt +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/assets/aria2tui_screenshot.png +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/assets/file_compare.png +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/assets/lpfman.png +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/examples/data_generation/list_files.toml +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/examples/data_generation/list_files_empty.toml +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/examples/data_generation/video_mediainfo.toml +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/examples/input_files/polynomials.tsv +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/examples/input_guides/gnuplot_graph.md +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/examples/picker/auxiallary_files/2024-25_Premier_League.pkl +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/examples/picker/footer_string_example.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/examples/picker/picker_example.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/examples/picker/template.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/examples/picker/wikipedia_table.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/listpick.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/requirements.txt +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/setup.cfg +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/__init__.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/__main__.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/pane/__init__.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/pane/get_data.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/pane/pane_functions.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/pane/pane_utils.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/ui/__init__.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/ui/build_help.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/ui/footer.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/ui/help_screen.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/ui/input_field.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/ui/keys.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/ui/picker_colours.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/__init__.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/clipboard_operations.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/config.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/dump.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/filtering.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/graphing.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/keycodes.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/options_selectors.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/paste_operations.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/picker_log.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/search_and_filter_utils.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/searching.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/sorting.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/table_to_list_of_lists.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/user_input.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick/utils/utils.py +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick.egg-info/dependency_links.txt +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick.egg-info/entry_points.txt +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick.egg-info/requires.txt +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/src/listpick.egg-info/top_level.txt +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/tests/kitty_control.sh +0 -0
- {listpick-0.1.16.0 → listpick-0.1.16.1}/tests/sorting_dates.csv +0 -0
|
@@ -1,10 +1,23 @@
|
|
|
1
1
|
# CHANGELOG.md
|
|
2
2
|
Note that the changes between 0.1.11.0 and 1.1.12.0 are listed under 0.1.11
|
|
3
3
|
|
|
4
|
-
## [0.1.16.
|
|
5
|
-
-
|
|
6
|
-
-
|
|
4
|
+
## [0.1.16.1] 2025-09-07
|
|
5
|
+
- Massive improvements to Picker data generation.
|
|
6
|
+
- Data generation is now multithreaded.
|
|
7
|
+
- ~5x quicker when getting information on video files
|
|
8
|
+
- Data generation is now done **asynchronously**.
|
|
9
|
+
- generate_picker_data sets the header and items of the Picker with only the Files column filled. The rest of the cells have "..." as a placeholder. Multiple threads are created and they generate data for each of the cells.
|
|
10
|
+
- The Picker can receive user interaction while the data loads.
|
|
11
|
+
- Implemented a task priority queue which the threads utilise to determine their next task.
|
|
12
|
+
- Cells that are in view are prioritised in the queue and are generated first.
|
|
13
|
+
- Created generate_picker_data_from_file() function and separated the generation-specific code into the original generate_picker_data() function.
|
|
14
|
+
- This allows one to utilise the new generation capabilities by simply passing a list of functions and filenames to the generate_picker_data() function--no toml file necessary.
|
|
15
|
+
- This has been implemented in lpfman to display file attributes.
|
|
16
|
+
- Bug fixes:
|
|
17
|
+
- Static footer string was not displayed in some cases.
|
|
7
18
|
|
|
19
|
+
## [0.1.16.1] 2025-09-05
|
|
20
|
+
- Data generation is now multithreaded.
|
|
8
21
|
|
|
9
22
|
## [0.1.15.20] 2025-08-31
|
|
10
23
|
- Fixed screen refresh function for default options selector.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: listpick
|
|
3
|
-
Version: 0.1.16.
|
|
3
|
+
Version: 0.1.16.1
|
|
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
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
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.
|
|
4
4
|
|
|
5
|
-
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()`.
|
|
5
|
+
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()`.
|
|
6
6
|
|
|
7
7
|
It works great as the backend for a TUI application and can also be used as a standalone data viewer.
|
|
8
8
|
|
|
@@ -10,13 +10,13 @@ It works great as the backend for a TUI application and can also be used as a st
|
|
|
10
10
|
|
|
11
11
|
# Quickstart
|
|
12
12
|
|
|
13
|
-
Install listpick
|
|
13
|
+
Install listpick:
|
|
14
14
|
|
|
15
15
|
```
|
|
16
16
|
python -m pip installl "listpick[full]"
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Create a
|
|
19
|
+
Create a Picker:
|
|
20
20
|
|
|
21
21
|
```
|
|
22
22
|
from listpick.listpick_app import Picker, start_curses, close_curses
|
|
@@ -36,7 +36,7 @@ x.run()
|
|
|
36
36
|
close_curses(stdscr)
|
|
37
37
|
|
|
38
38
|
```
|
|
39
|
-
|
|
39
|
+
Or use the listpick binary to generate and display rows based on a list of commands:
|
|
40
40
|
|
|
41
41
|
```
|
|
42
42
|
wget https://raw.githubusercontent.com/grimandgreedy/listpick/refs/heads/master/examples/data_generation/list_files.toml
|
|
@@ -28,6 +28,12 @@
|
|
|
28
28
|
> - [x] Selected column isn't highlighted properly when cell beginning is offscreen
|
|
29
29
|
> - [x] Header rows not aligned properly sometimes...
|
|
30
30
|
> - [ ] Check leftmost_char after resizing
|
|
31
|
+
> - [ ] Kill threads when we exit the picker.
|
|
32
|
+
> - [ ] Data locks when data is still being generated. No modifications.
|
|
33
|
+
> - [ ] (!!!) Cell cursor value with hidden columns
|
|
34
|
+
> - [ ] The selected column can be a hidden column
|
|
35
|
+
> - [ ] Hide all columns and toggle cell cursor mode. >> Hangs
|
|
36
|
+
> - [ ] Toggle last visible column in cell cursor mode. >> Hangs
|
|
31
37
|
|
|
32
38
|
|
|
33
39
|
|
|
@@ -7,31 +7,42 @@
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
[environment]
|
|
10
|
-
cwd="~/
|
|
11
|
-
# cwd="~/Videos/"
|
|
10
|
+
cwd="~/Videos/"
|
|
12
11
|
|
|
13
12
|
[data]
|
|
14
13
|
# files_command = "eza -1 --no-quotes | grep -E mp4$"
|
|
15
14
|
|
|
16
15
|
commands = [
|
|
16
|
+
# """find . -iname '*.m4v' -or -iname '*.webm'""",
|
|
17
17
|
"""eza -1 --no-quotes | grep -E mp4$""",
|
|
18
|
+
"""du -hs {} | awk '{{ print $1 }}'""",
|
|
19
|
+
|
|
18
20
|
# """sha1sum {} | awk '{{print $1}}'""",
|
|
21
|
+
|
|
22
|
+
## Duration, width, height methods
|
|
23
|
+
"""mediainfo --Inform="Video;%Duration/String%" {}""",
|
|
24
|
+
"""mediainfo --Inform="Video;%Width%" {}""",
|
|
25
|
+
"""mediainfo --Inform="Video;%Height%" {}""",
|
|
26
|
+
"""mediainfo --Inform="Video;%BitRate/String%" {}""",
|
|
27
|
+
"""mediainfo --Inform="Video;%CodecID%" {}""",
|
|
28
|
+
"""mediainfo --Inform="Video;%BitDepth%" {}""",
|
|
29
|
+
|
|
30
|
+
## Alternate duration, width, height methods
|
|
19
31
|
# """ffprobe -show_entries format=duration -v quiet -of csv="p=0" -i {}""",
|
|
20
32
|
# """ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=s=x:p=0 {}""",
|
|
21
33
|
# """ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 {}""",
|
|
22
|
-
"""mediainfo --Inform="Video;%Duration%" {}""",
|
|
23
|
-
"""mediainfo --Inform="Video;%Width%" {}""",
|
|
24
|
-
"""mediainfo --Inform="Video;%Height%" {}""",
|
|
25
|
-
"""du -hs {} | awk '{{ print $1 }}'""",
|
|
26
34
|
]
|
|
27
35
|
|
|
28
36
|
header = [
|
|
29
|
-
"
|
|
37
|
+
"File",
|
|
38
|
+
"Size",
|
|
30
39
|
# "sha1",
|
|
31
|
-
"
|
|
32
|
-
"
|
|
33
|
-
"
|
|
34
|
-
"
|
|
40
|
+
"Duration",
|
|
41
|
+
"Width",
|
|
42
|
+
"Height",
|
|
43
|
+
"Bit Rate",
|
|
44
|
+
"Codec",
|
|
45
|
+
"Bit Depth",
|
|
35
46
|
]
|
|
36
47
|
|
|
37
48
|
# run = [
|
|
@@ -16,7 +16,7 @@ with open("README.md", "r", encoding = "utf-8") as fh:
|
|
|
16
16
|
|
|
17
17
|
setuptools.setup(
|
|
18
18
|
name = "listpick",
|
|
19
|
-
version = "0.1.16.
|
|
19
|
+
version = "0.1.16.1",
|
|
20
20
|
author = "Grim",
|
|
21
21
|
author_email = "grimandgreedy@protonmail.com",
|
|
22
22
|
description = "Listpick is a powerful TUI data tool for creating TUI apps or viewing/comparing tabulated data.",
|
|
@@ -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)
|
|
@@ -2123,7 +2147,7 @@ class Picker:
|
|
|
2123
2147
|
|
|
2124
2148
|
self.stdscr = tmp
|
|
2125
2149
|
|
|
2126
|
-
self.notification(self.stdscr, f"{repr(file_to_load)} has been loaded!")
|
|
2150
|
+
# self.notification(self.stdscr, f"{repr(file_to_load)} has been loaded!")
|
|
2127
2151
|
|
|
2128
2152
|
self.set_function_data({}, reset_absent_variables=True)
|
|
2129
2153
|
self.load_file(self.loaded_file)
|
|
@@ -2146,7 +2170,9 @@ class Picker:
|
|
|
2146
2170
|
def fetch_data(self) -> None:
|
|
2147
2171
|
""" Refesh data asynchronously. When data has been fetched self.data_ready is set to True. """
|
|
2148
2172
|
self.logger.info(f"function: fetch_data()")
|
|
2149
|
-
tmp_items, tmp_header =
|
|
2173
|
+
tmp_items, tmp_header = [], []
|
|
2174
|
+
self.getting_data.clear()
|
|
2175
|
+
self.refresh_function(tmp_items, tmp_header, self.visible_rows_indices, self.getting_data)
|
|
2150
2176
|
if self.track_entries_upon_refresh:
|
|
2151
2177
|
selected_indices = get_selected_indices(self.selections)
|
|
2152
2178
|
self.ids = [item[self.id_column] for i, item in enumerate(self.items) if i in selected_indices]
|
|
@@ -2424,6 +2450,10 @@ class Picker:
|
|
|
2424
2450
|
else: return False
|
|
2425
2451
|
|
|
2426
2452
|
COLS, LINES = os.get_terminal_size()
|
|
2453
|
+
|
|
2454
|
+
|
|
2455
|
+
getting_data_prev = False
|
|
2456
|
+
|
|
2427
2457
|
# Main loop
|
|
2428
2458
|
while True:
|
|
2429
2459
|
# key = self.stdscr.getch()
|
|
@@ -2432,6 +2462,16 @@ class Picker:
|
|
|
2432
2462
|
if key != -1:
|
|
2433
2463
|
self.logger.info(f"key={key}")
|
|
2434
2464
|
|
|
2465
|
+
# Ensure that
|
|
2466
|
+
|
|
2467
|
+
if not self.getting_data.is_set():
|
|
2468
|
+
self.initialise_variables()
|
|
2469
|
+
getting_data_prev = True
|
|
2470
|
+
elif getting_data_prev:
|
|
2471
|
+
## Ensure that we reinitialise one final time after all data is retrieved.
|
|
2472
|
+
self.initialise_variables()
|
|
2473
|
+
getting_data_prev = False
|
|
2474
|
+
|
|
2435
2475
|
self.term_resize_event = terminal_resized(COLS, LINES)
|
|
2436
2476
|
COLS, LINES = os.get_terminal_size()
|
|
2437
2477
|
if self.term_resize_event:
|
|
@@ -2950,8 +2990,8 @@ class Picker:
|
|
|
2950
2990
|
|
|
2951
2991
|
|
|
2952
2992
|
## Scroll with column select
|
|
2953
|
-
|
|
2954
|
-
self.column_widths = get_column_widths(
|
|
2993
|
+
self.get_visible_rows()
|
|
2994
|
+
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
2995
|
visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
|
|
2956
2996
|
column_set_width = sum(visible_column_widths)+len(self.separator)*len(visible_column_widths)
|
|
2957
2997
|
start_of_cell = sum(visible_column_widths[:self.selected_column])+len(self.separator)*self.selected_column
|
|
@@ -2977,8 +3017,8 @@ class Picker:
|
|
|
2977
3017
|
# curses.flash()
|
|
2978
3018
|
|
|
2979
3019
|
## Scroll with column select
|
|
2980
|
-
|
|
2981
|
-
self.column_widths = get_column_widths(
|
|
3020
|
+
self.get_visible_rows()
|
|
3021
|
+
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
3022
|
visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
|
|
2983
3023
|
column_set_width = sum(visible_column_widths)+len(self.separator)*len(visible_column_widths)
|
|
2984
3024
|
start_of_cell = sum(visible_column_widths[:self.selected_column])+len(self.separator)*self.selected_column
|
|
@@ -3708,7 +3748,7 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
|
|
|
3708
3748
|
# input_arg = args.filename
|
|
3709
3749
|
|
|
3710
3750
|
elif args.generate:
|
|
3711
|
-
function_data["refresh_function"] = lambda :
|
|
3751
|
+
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
3752
|
function_data["get_data_startup"] = True
|
|
3713
3753
|
function_data["get_new_data"] = True
|
|
3714
3754
|
return args, function_data
|
|
@@ -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.1
|
|
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
|
|
@@ -46,6 +46,7 @@ src/listpick/utils/config.py
|
|
|
46
46
|
src/listpick/utils/dump.py
|
|
47
47
|
src/listpick/utils/filtering.py
|
|
48
48
|
src/listpick/utils/generate_data.py
|
|
49
|
+
src/listpick/utils/generate_data_multithreaded.py
|
|
49
50
|
src/listpick/utils/graphing.py
|
|
50
51
|
src/listpick/utils/keycodes.py
|
|
51
52
|
src/listpick/utils/options_selectors.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{listpick-0.1.16.0 → listpick-0.1.16.1}/examples/picker/auxiallary_files/2024-25_Premier_League.pkl
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|