listpick 0.1.15.20__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.15.20 → listpick-0.1.16.1}/CHANGELOG.md +19 -0
- {listpick-0.1.15.20/src/listpick.egg-info → listpick-0.1.16.1}/PKG-INFO +5 -5
- {listpick-0.1.15.20 → listpick-0.1.16.1}/README.md +4 -4
- {listpick-0.1.15.20 → listpick-0.1.16.1}/TODO.md +7 -0
- listpick-0.1.16.1/examples/data_generation/video_duplicates.toml +50 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/setup.py +1 -1
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/listpick_app.py +78 -27
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/pane/get_data.py +20 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/pane/pane_functions.py +45 -5
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/pane/pane_utils.py +1 -1
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/generate_data.py +37 -1
- listpick-0.1.16.1/src/listpick/utils/generate_data_multithreaded.py +202 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1/src/listpick.egg-info}/PKG-INFO +5 -5
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick.egg-info/SOURCES.txt +1 -0
- listpick-0.1.15.20/examples/data_generation/video_duplicates.toml +0 -36
- {listpick-0.1.15.20 → listpick-0.1.16.1}/.gitignore +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/LICENSE.txt +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/assets/aria2tui_screenshot.png +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/assets/file_compare.png +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/assets/lpfman.png +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/data_generation/list_files.toml +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/data_generation/list_files_empty.toml +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/data_generation/video_mediainfo.toml +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/input_files/polynomials.tsv +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/input_guides/gnuplot_graph.md +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/picker/auxiallary_files/2024-25_Premier_League.pkl +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/picker/footer_string_example.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/picker/picker_example.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/picker/template.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/examples/picker/wikipedia_table.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/listpick.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/requirements.txt +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/setup.cfg +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/__init__.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/__main__.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/pane/__init__.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/ui/__init__.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/ui/build_help.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/ui/footer.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/ui/help_screen.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/ui/input_field.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/ui/keys.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/ui/picker_colours.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/__init__.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/clipboard_operations.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/config.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/dump.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/filtering.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/graphing.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/keycodes.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/options_selectors.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/paste_operations.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/picker_log.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/search_and_filter_utils.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/searching.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/sorting.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/table_to_list_of_lists.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/user_input.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick/utils/utils.py +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick.egg-info/dependency_links.txt +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick.egg-info/entry_points.txt +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick.egg-info/requires.txt +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/src/listpick.egg-info/top_level.txt +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/tests/kitty_control.sh +0 -0
- {listpick-0.1.15.20 → listpick-0.1.16.1}/tests/sorting_dates.csv +0 -0
|
@@ -1,5 +1,24 @@
|
|
|
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
|
+
|
|
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.
|
|
18
|
+
|
|
19
|
+
## [0.1.16.1] 2025-09-05
|
|
20
|
+
- Data generation is now multithreaded.
|
|
21
|
+
|
|
3
22
|
## [0.1.15.20] 2025-08-31
|
|
4
23
|
- Fixed screen refresh function for default options selector.
|
|
5
24
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: listpick
|
|
3
|
-
Version: 0.1.
|
|
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
|
|
|
@@ -388,6 +394,7 @@
|
|
|
388
394
|
> - [x] Update footer height when there is a search query.
|
|
389
395
|
> - [x] Header columns are not aligned with long header values.
|
|
390
396
|
> - [x] Done: 2025-08-27
|
|
397
|
+
> - [ ] Pressing page forward/page back in an empty picker changes the index in the footer from 0 to 1...
|
|
391
398
|
|
|
392
399
|
|
|
393
400
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#
|
|
2
|
+
# video_duplicates.toml
|
|
3
|
+
# Generate list of mp4 files along with their: sha1, duration, width, height, and size.
|
|
4
|
+
#
|
|
5
|
+
# Author: GrimAndGreedy
|
|
6
|
+
# License: MIT
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
[environment]
|
|
10
|
+
cwd="~/Videos/"
|
|
11
|
+
|
|
12
|
+
[data]
|
|
13
|
+
# files_command = "eza -1 --no-quotes | grep -E mp4$"
|
|
14
|
+
|
|
15
|
+
commands = [
|
|
16
|
+
# """find . -iname '*.m4v' -or -iname '*.webm'""",
|
|
17
|
+
"""eza -1 --no-quotes | grep -E mp4$""",
|
|
18
|
+
"""du -hs {} | awk '{{ print $1 }}'""",
|
|
19
|
+
|
|
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
|
|
31
|
+
# """ffprobe -show_entries format=duration -v quiet -of csv="p=0" -i {}""",
|
|
32
|
+
# """ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=s=x:p=0 {}""",
|
|
33
|
+
# """ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 {}""",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
header = [
|
|
37
|
+
"File",
|
|
38
|
+
"Size",
|
|
39
|
+
# "sha1",
|
|
40
|
+
"Duration",
|
|
41
|
+
"Width",
|
|
42
|
+
"Height",
|
|
43
|
+
"Bit Rate",
|
|
44
|
+
"Codec",
|
|
45
|
+
"Bit Depth",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
# run = [
|
|
49
|
+
# "mpv",
|
|
50
|
+
# ]
|
|
@@ -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.
|
|
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,13 +36,14 @@ 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
|
|
43
44
|
from listpick.utils.picker_log import setup_logger
|
|
44
45
|
from listpick.utils.user_input import get_char, open_tty
|
|
45
|
-
from listpick.pane.pane_functions import right_split_file_attributes, right_split_graph, right_split_display_list
|
|
46
|
+
from listpick.pane.pane_functions import right_split_file_attributes, right_split_file_attributes_dynamic, right_split_graph, right_split_display_list
|
|
46
47
|
from listpick.pane.get_data import *
|
|
47
48
|
|
|
48
49
|
|
|
@@ -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,
|
|
@@ -127,17 +128,18 @@ class Picker:
|
|
|
127
128
|
columns_sort_method: list[int] = [0],
|
|
128
129
|
key_chain: str = "",
|
|
129
130
|
last_key: Optional[str] = None,
|
|
131
|
+
disabled_keys: list=[],
|
|
130
132
|
|
|
131
133
|
paginate: bool =False,
|
|
132
134
|
cancel_is_back: bool = False,
|
|
133
135
|
mode_index: int = 0,
|
|
134
136
|
modes: list[dict] = [],
|
|
135
|
-
display_modes: bool =False,
|
|
136
|
-
require_option: list=[],
|
|
137
|
-
require_option_default:
|
|
137
|
+
display_modes: bool = False,
|
|
138
|
+
require_option: list[bool] = [],
|
|
139
|
+
require_option_default: bool = False,
|
|
138
140
|
option_functions: list[Callable[..., Tuple[bool, str]]] = [],
|
|
139
141
|
default_option_function: Callable[..., Tuple[bool, str]] = default_option_input,
|
|
140
|
-
|
|
142
|
+
|
|
141
143
|
|
|
142
144
|
show_header: bool = True,
|
|
143
145
|
show_row_header: bool = False,
|
|
@@ -145,7 +147,7 @@ class Picker:
|
|
|
145
147
|
footer_style: int = 0,
|
|
146
148
|
footer_string: str="",
|
|
147
149
|
footer_string_auto_refresh: bool=False,
|
|
148
|
-
footer_string_refresh_function: Optional[Callable] = None,
|
|
150
|
+
footer_string_refresh_function: Optional[Callable] = lambda : None,
|
|
149
151
|
footer_timer: float=1,
|
|
150
152
|
get_footer_string_startup=False,
|
|
151
153
|
unicode_char_width: bool = True,
|
|
@@ -195,6 +197,9 @@ class Picker:
|
|
|
195
197
|
split_right: bool = False,
|
|
196
198
|
right_panes: list = [],
|
|
197
199
|
right_pane_index: int = 0,
|
|
200
|
+
|
|
201
|
+
# getting_data: threading.Event = threading.Event(),
|
|
202
|
+
|
|
198
203
|
):
|
|
199
204
|
self.stdscr = stdscr
|
|
200
205
|
self.items = items
|
|
@@ -280,7 +285,7 @@ class Picker:
|
|
|
280
285
|
self.footer_string_auto_refresh = footer_string_auto_refresh
|
|
281
286
|
self.footer_string_refresh_function = footer_string_refresh_function
|
|
282
287
|
self.footer_timer = footer_timer
|
|
283
|
-
self.get_footer_string_startup = get_footer_string_startup
|
|
288
|
+
self.get_footer_string_startup = get_footer_string_startup
|
|
284
289
|
self.unicode_char_width = unicode_char_width
|
|
285
290
|
|
|
286
291
|
|
|
@@ -312,7 +317,6 @@ class Picker:
|
|
|
312
317
|
|
|
313
318
|
|
|
314
319
|
# Refresh function variables
|
|
315
|
-
self.data_refreshed = False
|
|
316
320
|
self.refreshing_data = False
|
|
317
321
|
self.data_lock = threading.Lock()
|
|
318
322
|
self.data_ready = False
|
|
@@ -347,12 +351,22 @@ class Picker:
|
|
|
347
351
|
self.split_right = split_right
|
|
348
352
|
self.right_panes = right_panes
|
|
349
353
|
self.right_pane_index = right_pane_index
|
|
354
|
+
self.visible_rows_indices = []
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
|
|
350
359
|
self.initialise_picker_state(reset_colours=self.reset_colours)
|
|
351
360
|
|
|
352
361
|
# Note: We have to set the footer after initialising the picker state so that the footer can use the get_function_data method
|
|
353
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)]
|
|
354
363
|
self.footer = self.footer_options[self.footer_style]
|
|
355
|
-
|
|
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()
|
|
356
370
|
|
|
357
371
|
def __sizeof__(self):
|
|
358
372
|
|
|
@@ -461,8 +475,11 @@ class Picker:
|
|
|
461
475
|
end_index = min(start_index + self.items_per_page, len(self.indexed_items))
|
|
462
476
|
if len(self.indexed_items) == 0: start_index, end_index = 0, 0
|
|
463
477
|
|
|
464
|
-
|
|
465
|
-
|
|
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
|
|
466
483
|
|
|
467
484
|
def initialise_picker_state(self, reset_colours=False) -> None:
|
|
468
485
|
""" Initialise state variables for the picker. These are: debugging and colours. """
|
|
@@ -556,7 +573,10 @@ class Picker:
|
|
|
556
573
|
self.cursor_pos_id = self.indexed_items[self.cursor_pos][1][self.id_column]
|
|
557
574
|
self.cursor_pos_prev = self.cursor_pos
|
|
558
575
|
|
|
559
|
-
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
self.getting_data.clear()
|
|
579
|
+
self.refresh_function(self.items, self.header, self.visible_rows_indices, self.getting_data)
|
|
560
580
|
|
|
561
581
|
self.items = pad_lists_to_same_length(self.items)
|
|
562
582
|
|
|
@@ -564,7 +584,7 @@ class Picker:
|
|
|
564
584
|
## Ensure that items is a List[List[Str]] object
|
|
565
585
|
if len(self.items) > 0 and not isinstance(self.items[0], list):
|
|
566
586
|
self.items = [[item] for item in self.items]
|
|
567
|
-
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]
|
|
568
588
|
|
|
569
589
|
|
|
570
590
|
# Ensure that header is of the same length as the rows
|
|
@@ -818,8 +838,9 @@ class Picker:
|
|
|
818
838
|
# Determine widths based only on the currently indexed rows
|
|
819
839
|
# rows = [v[1] for v in self.indexed_items] if len(self.indexed_items) else self.items
|
|
820
840
|
# Determine widths based only on the currently displayed indexed rows
|
|
821
|
-
rows = [v[1] for v in self.indexed_items[start_index:end_index]] if len(self.indexed_items) else self.items
|
|
822
|
-
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)
|
|
823
844
|
visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
|
|
824
845
|
visible_columns_total_width = sum(visible_column_widths) + len(self.separator)*(len(visible_column_widths)-1)
|
|
825
846
|
|
|
@@ -1197,6 +1218,11 @@ class Picker:
|
|
|
1197
1218
|
else:
|
|
1198
1219
|
self.stdscr.addstr(0,self.term_w-3," ", curses.color_pair(self.colours_start+23) | curses.A_BOLD)
|
|
1199
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
|
+
|
|
1200
1226
|
## Display footer
|
|
1201
1227
|
if self.show_footer:
|
|
1202
1228
|
# self.footer = NoFooter(self.stdscr, self.colours_start, self.get_function_data)
|
|
@@ -2121,7 +2147,7 @@ class Picker:
|
|
|
2121
2147
|
|
|
2122
2148
|
self.stdscr = tmp
|
|
2123
2149
|
|
|
2124
|
-
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!")
|
|
2125
2151
|
|
|
2126
2152
|
self.set_function_data({}, reset_absent_variables=True)
|
|
2127
2153
|
self.load_file(self.loaded_file)
|
|
@@ -2144,7 +2170,9 @@ class Picker:
|
|
|
2144
2170
|
def fetch_data(self) -> None:
|
|
2145
2171
|
""" Refesh data asynchronously. When data has been fetched self.data_ready is set to True. """
|
|
2146
2172
|
self.logger.info(f"function: fetch_data()")
|
|
2147
|
-
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)
|
|
2148
2176
|
if self.track_entries_upon_refresh:
|
|
2149
2177
|
selected_indices = get_selected_indices(self.selections)
|
|
2150
2178
|
self.ids = [item[self.id_column] for i, item in enumerate(self.items) if i in selected_indices]
|
|
@@ -2422,6 +2450,10 @@ class Picker:
|
|
|
2422
2450
|
else: return False
|
|
2423
2451
|
|
|
2424
2452
|
COLS, LINES = os.get_terminal_size()
|
|
2453
|
+
|
|
2454
|
+
|
|
2455
|
+
getting_data_prev = False
|
|
2456
|
+
|
|
2425
2457
|
# Main loop
|
|
2426
2458
|
while True:
|
|
2427
2459
|
# key = self.stdscr.getch()
|
|
@@ -2430,6 +2462,16 @@ class Picker:
|
|
|
2430
2462
|
if key != -1:
|
|
2431
2463
|
self.logger.info(f"key={key}")
|
|
2432
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
|
+
|
|
2433
2475
|
self.term_resize_event = terminal_resized(COLS, LINES)
|
|
2434
2476
|
COLS, LINES = os.get_terminal_size()
|
|
2435
2477
|
if self.term_resize_event:
|
|
@@ -2948,8 +2990,8 @@ class Picker:
|
|
|
2948
2990
|
|
|
2949
2991
|
|
|
2950
2992
|
## Scroll with column select
|
|
2951
|
-
|
|
2952
|
-
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)
|
|
2953
2995
|
visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
|
|
2954
2996
|
column_set_width = sum(visible_column_widths)+len(self.separator)*len(visible_column_widths)
|
|
2955
2997
|
start_of_cell = sum(visible_column_widths[:self.selected_column])+len(self.separator)*self.selected_column
|
|
@@ -2975,8 +3017,8 @@ class Picker:
|
|
|
2975
3017
|
# curses.flash()
|
|
2976
3018
|
|
|
2977
3019
|
## Scroll with column select
|
|
2978
|
-
|
|
2979
|
-
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)
|
|
2980
3022
|
visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
|
|
2981
3023
|
column_set_width = sum(visible_column_widths)+len(self.separator)*len(visible_column_widths)
|
|
2982
3024
|
start_of_cell = sum(visible_column_widths[:self.selected_column])+len(self.separator)*self.selected_column
|
|
@@ -3706,7 +3748,7 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
|
|
|
3706
3748
|
# input_arg = args.filename
|
|
3707
3749
|
|
|
3708
3750
|
elif args.generate:
|
|
3709
|
-
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)
|
|
3710
3752
|
function_data["get_data_startup"] = True
|
|
3711
3753
|
function_data["get_new_data"] = True
|
|
3712
3754
|
return args, function_data
|
|
@@ -3975,7 +4017,7 @@ def main() -> None:
|
|
|
3975
4017
|
# function_data["debug_level"] = 1
|
|
3976
4018
|
|
|
3977
4019
|
function_data["split_right"] = False
|
|
3978
|
-
function_data["right_pane_index"] =
|
|
4020
|
+
function_data["right_pane_index"] = 2
|
|
3979
4021
|
|
|
3980
4022
|
function_data["right_panes"] = [
|
|
3981
4023
|
# Graph or random numbers generated each second
|
|
@@ -3996,15 +4038,24 @@ def main() -> None:
|
|
|
3996
4038
|
"data": ["Files", [str(x) for x in range(100)]],
|
|
3997
4039
|
"refresh_time": 1.0,
|
|
3998
4040
|
},
|
|
3999
|
-
# File
|
|
4041
|
+
# File attributes
|
|
4000
4042
|
{
|
|
4001
4043
|
"proportion": 2/3,
|
|
4002
4044
|
"auto_refresh": False,
|
|
4003
4045
|
"get_data": lambda data, state: [],
|
|
4004
4046
|
"display": right_split_file_attributes,
|
|
4005
|
-
"data": [
|
|
4047
|
+
"data": [],
|
|
4006
4048
|
"refresh_time": 1.0,
|
|
4007
4049
|
},
|
|
4050
|
+
# File attributes dynamic
|
|
4051
|
+
{
|
|
4052
|
+
"proportion": 2/3,
|
|
4053
|
+
"auto_refresh": True,
|
|
4054
|
+
"get_data": update_file_attributes,
|
|
4055
|
+
"display": right_split_file_attributes_dynamic,
|
|
4056
|
+
"data": [],
|
|
4057
|
+
"refresh_time": 2.0,
|
|
4058
|
+
},
|
|
4008
4059
|
# List of random numbers generated each second
|
|
4009
4060
|
{
|
|
4010
4061
|
"proportion": 1/2,
|
|
@@ -8,6 +8,8 @@ Author: GrimAndGreedy
|
|
|
8
8
|
License: MIT
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
|
+
from listpick.pane.pane_utils import get_file_attributes
|
|
12
|
+
|
|
11
13
|
def data_refresh_randint_by_row(data, state):
|
|
12
14
|
"""
|
|
13
15
|
Add a random number to the data if row id is the same.
|
|
@@ -93,3 +95,21 @@ def get_dl(data, state):
|
|
|
93
95
|
data[0].append(data[0][-1]+1)
|
|
94
96
|
data[1].append(dl)
|
|
95
97
|
return data
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def update_file_attributes(data, state):
|
|
101
|
+
"""
|
|
102
|
+
Get file attributes
|
|
103
|
+
|
|
104
|
+
data[0]: ["size: {}", filetype: {}, last modified: {}]
|
|
105
|
+
]
|
|
106
|
+
data[1]: id
|
|
107
|
+
"""
|
|
108
|
+
if state["indexed_items"]:
|
|
109
|
+
# id = state["indexed_items"][state["cursor_pos"]][1][state["id_column"]]
|
|
110
|
+
id = state["indexed_items"][state["cursor_pos"]][1][0]
|
|
111
|
+
else:
|
|
112
|
+
return [[], -1]
|
|
113
|
+
return [get_file_attributes(id), id]
|
|
114
|
+
|
|
115
|
+
|
|
@@ -11,15 +11,14 @@ License: MIT
|
|
|
11
11
|
import curses
|
|
12
12
|
import os
|
|
13
13
|
from listpick.pane.pane_utils import get_file_attributes, get_graph_string, escape_ansi
|
|
14
|
+
from listpick.pane.get_data import update_file_attributes
|
|
14
15
|
|
|
15
|
-
def right_split_file_attributes(stdscr, x, y, w, h, state, row, cell,
|
|
16
|
+
def right_split_file_attributes(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
|
|
16
17
|
"""
|
|
17
18
|
Display file attributes in right pane.
|
|
18
19
|
"""
|
|
19
20
|
if test: return True
|
|
20
21
|
|
|
21
|
-
os.chdir(os.path.expanduser("~/Downloads/new"))
|
|
22
|
-
|
|
23
22
|
# Title
|
|
24
23
|
title = "File attributes"
|
|
25
24
|
if len(title) < w: title = f"{title:^{w}}"
|
|
@@ -45,7 +44,48 @@ def right_split_file_attributes(stdscr, x, y, w, h, state, row, cell, past_data:
|
|
|
45
44
|
|
|
46
45
|
return []
|
|
47
46
|
|
|
48
|
-
|
|
47
|
+
|
|
48
|
+
def right_split_file_attributes_dynamic(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
|
|
49
|
+
"""
|
|
50
|
+
Display file attributes in right pane.
|
|
51
|
+
"""
|
|
52
|
+
if test: return True
|
|
53
|
+
|
|
54
|
+
# Title
|
|
55
|
+
title = "File attributes"
|
|
56
|
+
if len(title) < w: title = f"{title:^{w}}"
|
|
57
|
+
stdscr.addstr(y, x,title[:w], curses.color_pair(state["colours_start"]+4) | curses.A_BOLD)
|
|
58
|
+
|
|
59
|
+
# Separator
|
|
60
|
+
for j in range(h):
|
|
61
|
+
stdscr.addstr(j+y, x, ' ', curses.color_pair(state["colours_start"]+16))
|
|
62
|
+
|
|
63
|
+
# Display pane count
|
|
64
|
+
pane_count = len(state["right_panes"])
|
|
65
|
+
pane_index = state["right_pane_index"]
|
|
66
|
+
if pane_count > 1:
|
|
67
|
+
s = f" {pane_index+1}/{pane_count} "
|
|
68
|
+
stdscr.addstr(y+h-1, x+w-len(s)-1, s, curses.color_pair(state["colours_start"]+20))
|
|
69
|
+
|
|
70
|
+
if len(state["indexed_items"]) == 0:
|
|
71
|
+
return []
|
|
72
|
+
|
|
73
|
+
# Filename/cursor cell value
|
|
74
|
+
stdscr.addstr(y+2, x+2, cell[:w-3])
|
|
75
|
+
|
|
76
|
+
# If the cursor-hovered file is different then reload the data
|
|
77
|
+
if data[1] != cell:
|
|
78
|
+
data[:] = update_file_attributes(data, state)
|
|
79
|
+
|
|
80
|
+
# attributes = get_file_attributes(cell)
|
|
81
|
+
if len(data) == 0: return []
|
|
82
|
+
attributes = data[0]
|
|
83
|
+
for i, attr in enumerate(attributes):
|
|
84
|
+
stdscr.addstr(y+3+i, x+4, attr[:w-5])
|
|
85
|
+
|
|
86
|
+
return []
|
|
87
|
+
|
|
88
|
+
def right_split_graph(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
|
|
49
89
|
"""
|
|
50
90
|
Display a graph of the data in right pane.
|
|
51
91
|
|
|
@@ -95,7 +135,7 @@ def right_split_graph(stdscr, x, y, w, h, state, row, cell, past_data: list = []
|
|
|
95
135
|
|
|
96
136
|
|
|
97
137
|
|
|
98
|
-
def right_split_display_list(stdscr, x, y, w, h, state, row, cell,
|
|
138
|
+
def right_split_display_list(stdscr, x, y, w, h, state, row, cell, data: list = [], test: bool = False):
|
|
99
139
|
"""
|
|
100
140
|
data[0]:str = title
|
|
101
141
|
data[1]:list[str] = list of strings to display
|
|
@@ -15,8 +15,44 @@ import toml
|
|
|
15
15
|
import logging
|
|
16
16
|
|
|
17
17
|
logger = logging.getLogger('picker_log')
|
|
18
|
+
import concurrent.futures
|
|
18
19
|
|
|
19
20
|
def generate_columns(funcs: list, files: list) -> list[list[str]]:
|
|
21
|
+
"""
|
|
22
|
+
Takes a list of functions and a list of files.
|
|
23
|
+
Tasks are run in parallel using concurrent.futures.
|
|
24
|
+
"""
|
|
25
|
+
logger.info("function: generate_columns (generate_data.py)")
|
|
26
|
+
|
|
27
|
+
results = []
|
|
28
|
+
# Create a future object for each combination of func and file
|
|
29
|
+
with concurrent.futures.ThreadPoolExecutor() as executor:
|
|
30
|
+
futures = [[executor.submit(func, file) for func in funcs] for file in files]
|
|
31
|
+
|
|
32
|
+
for file_futures in futures:
|
|
33
|
+
result = [future.result() for future in file_futures]
|
|
34
|
+
results.append(result)
|
|
35
|
+
return results
|
|
36
|
+
|
|
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
|
+
|
|
55
|
+
def generate_columns_single_thread(funcs: list, files: list) -> list[list[str]]:
|
|
20
56
|
"""
|
|
21
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.
|
|
22
58
|
"""
|
|
@@ -30,7 +66,7 @@ def generate_columns(funcs: list, files: list) -> list[list[str]]:
|
|
|
30
66
|
except:
|
|
31
67
|
item.append("")
|
|
32
68
|
items.append(item)
|
|
33
|
-
|
|
69
|
+
|
|
34
70
|
return items
|
|
35
71
|
|
|
36
72
|
def command_to_func(command: str) -> Callable:
|
|
@@ -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.
|
|
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
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
#
|
|
2
|
-
# video_duplicates.toml
|
|
3
|
-
# Generate list of mp4 files along with their: sha1, duration, width, height, and size.
|
|
4
|
-
#
|
|
5
|
-
# Author: GrimAndGreedy
|
|
6
|
-
# License: MIT
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
[environment]
|
|
10
|
-
cwd="~/Downloads/new/creators/ashla/"
|
|
11
|
-
# cwd="~/Videos/"
|
|
12
|
-
|
|
13
|
-
[data]
|
|
14
|
-
files_command = "eza -1 --no-quotes | grep -E mp4$"
|
|
15
|
-
|
|
16
|
-
commands = [
|
|
17
|
-
"""eza -1 --no-quotes | grep -E mp4$""",
|
|
18
|
-
# """sha1sum {} | awk '{{print $1}}'""",
|
|
19
|
-
"""ffprobe -show_entries format=duration -v quiet -of csv="p=0" -i {}""",
|
|
20
|
-
"""ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=s=x:p=0 {}""",
|
|
21
|
-
"""ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 {}""",
|
|
22
|
-
"""du -hs {} | awk '{{ print $1 }}'"""
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
header = [
|
|
26
|
-
"file",
|
|
27
|
-
# "sha1",
|
|
28
|
-
"duration",
|
|
29
|
-
"width",
|
|
30
|
-
"height",
|
|
31
|
-
"size",
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
# run = [
|
|
35
|
-
# "mpv",
|
|
36
|
-
# ]
|
|
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.15.20 → 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
|