listpick 0.1.16.0__tar.gz → 0.1.16.3__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.

Files changed (64) hide show
  1. {listpick-0.1.16.0 → listpick-0.1.16.3}/CHANGELOG.md +19 -3
  2. {listpick-0.1.16.0/src/listpick.egg-info → listpick-0.1.16.3}/PKG-INFO +5 -5
  3. {listpick-0.1.16.0 → listpick-0.1.16.3}/README.md +4 -4
  4. {listpick-0.1.16.0 → listpick-0.1.16.3}/TODO.md +6 -0
  5. {listpick-0.1.16.0 → listpick-0.1.16.3}/examples/data_generation/video_duplicates.toml +22 -11
  6. {listpick-0.1.16.0 → listpick-0.1.16.3}/setup.py +1 -1
  7. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/listpick_app.py +60 -19
  8. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/ui/footer.py +3 -3
  9. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/generate_data.py +17 -0
  10. listpick-0.1.16.3/src/listpick/utils/generate_data_multithreaded.py +202 -0
  11. {listpick-0.1.16.0 → listpick-0.1.16.3/src/listpick.egg-info}/PKG-INFO +5 -5
  12. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick.egg-info/SOURCES.txt +1 -0
  13. {listpick-0.1.16.0 → listpick-0.1.16.3}/.gitignore +0 -0
  14. {listpick-0.1.16.0 → listpick-0.1.16.3}/LICENSE.txt +0 -0
  15. {listpick-0.1.16.0 → listpick-0.1.16.3}/assets/aria2tui_screenshot.png +0 -0
  16. {listpick-0.1.16.0 → listpick-0.1.16.3}/assets/file_compare.png +0 -0
  17. {listpick-0.1.16.0 → listpick-0.1.16.3}/assets/lpfman.png +0 -0
  18. {listpick-0.1.16.0 → listpick-0.1.16.3}/examples/data_generation/list_files.toml +0 -0
  19. {listpick-0.1.16.0 → listpick-0.1.16.3}/examples/data_generation/list_files_empty.toml +0 -0
  20. {listpick-0.1.16.0 → listpick-0.1.16.3}/examples/data_generation/video_mediainfo.toml +0 -0
  21. {listpick-0.1.16.0 → listpick-0.1.16.3}/examples/input_files/polynomials.tsv +0 -0
  22. {listpick-0.1.16.0 → listpick-0.1.16.3}/examples/input_guides/gnuplot_graph.md +0 -0
  23. {listpick-0.1.16.0 → listpick-0.1.16.3}/examples/picker/auxiallary_files/2024-25_Premier_League.pkl +0 -0
  24. {listpick-0.1.16.0 → listpick-0.1.16.3}/examples/picker/footer_string_example.py +0 -0
  25. {listpick-0.1.16.0 → listpick-0.1.16.3}/examples/picker/picker_example.py +0 -0
  26. {listpick-0.1.16.0 → listpick-0.1.16.3}/examples/picker/template.py +0 -0
  27. {listpick-0.1.16.0 → listpick-0.1.16.3}/examples/picker/wikipedia_table.py +0 -0
  28. {listpick-0.1.16.0 → listpick-0.1.16.3}/listpick.py +0 -0
  29. {listpick-0.1.16.0 → listpick-0.1.16.3}/requirements.txt +0 -0
  30. {listpick-0.1.16.0 → listpick-0.1.16.3}/setup.cfg +0 -0
  31. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/__init__.py +0 -0
  32. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/__main__.py +0 -0
  33. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/pane/__init__.py +0 -0
  34. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/pane/get_data.py +0 -0
  35. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/pane/pane_functions.py +0 -0
  36. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/pane/pane_utils.py +0 -0
  37. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/ui/__init__.py +0 -0
  38. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/ui/build_help.py +0 -0
  39. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/ui/help_screen.py +0 -0
  40. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/ui/input_field.py +0 -0
  41. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/ui/keys.py +0 -0
  42. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/ui/picker_colours.py +0 -0
  43. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/__init__.py +0 -0
  44. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/clipboard_operations.py +0 -0
  45. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/config.py +0 -0
  46. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/dump.py +0 -0
  47. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/filtering.py +0 -0
  48. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/graphing.py +0 -0
  49. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/keycodes.py +0 -0
  50. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/options_selectors.py +0 -0
  51. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/paste_operations.py +0 -0
  52. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/picker_log.py +0 -0
  53. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/search_and_filter_utils.py +0 -0
  54. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/searching.py +0 -0
  55. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/sorting.py +0 -0
  56. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/table_to_list_of_lists.py +0 -0
  57. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/user_input.py +0 -0
  58. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick/utils/utils.py +0 -0
  59. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick.egg-info/dependency_links.txt +0 -0
  60. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick.egg-info/entry_points.txt +0 -0
  61. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick.egg-info/requires.txt +0 -0
  62. {listpick-0.1.16.0 → listpick-0.1.16.3}/src/listpick.egg-info/top_level.txt +0 -0
  63. {listpick-0.1.16.0 → listpick-0.1.16.3}/tests/kitty_control.sh +0 -0
  64. {listpick-0.1.16.0 → listpick-0.1.16.3}/tests/sorting_dates.csv +0 -0
@@ -1,10 +1,26 @@
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.0] 2025-09-05
5
- - Data generation is now multithreaded.
6
- - ~5x quicker when getting data for video files
4
+ ## [0.1.16.2] 2025-09-07
5
+ - Fixed some f-strings with nested quotes.
6
+
7
+ ## [0.1.16.1] 2025-09-07
8
+ - Massive improvements to Picker data generation.
9
+ - Data generation is now multithreaded.
10
+ - ~5x quicker when getting information on video files
11
+ - Data generation is now done **asynchronously**.
12
+ - 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.
13
+ - The Picker can receive user interaction while the data loads.
14
+ - Implemented a task priority queue which the threads utilise to determine their next task.
15
+ - Cells that are in view are prioritised in the queue and are generated first.
16
+ - Created generate_picker_data_from_file() function and separated the generation-specific code into the original generate_picker_data() function.
17
+ - 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.
18
+ - This has been implemented in lpfman to display file attributes.
19
+ - Bug fixes:
20
+ - Static footer string was not displayed in some cases.
7
21
 
22
+ ## [0.1.16.1] 2025-09-05
23
+ - Data generation is now multithreaded.
8
24
 
9
25
  ## [0.1.15.20] 2025-08-31
10
26
  - 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.0
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 picker object to display rows:
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
- Use the listpick binary to generate and display rows based on a list of commands:
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 picker object to display rows:
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
- Use the listpick binary to generate and display rows based on a list of commands:
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="~/Downloads/new"
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
- "file",
37
+ "File",
38
+ "Size",
30
39
  # "sha1",
31
- "duration",
32
- "width",
33
- "height",
34
- "size",
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.0",
19
+ version = "0.1.16.3",
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: [], # The function with which we get new data
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
- self.__version__ = "1.0"
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
- rows = [v[1] for v in self.indexed_items[start_index:end_index]] if len(self.indexed_items) else self.items
467
- return rows
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
- self.items, self.header = self.refresh_function()
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.column_widths = get_column_widths(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)
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 = self.refresh_function()
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
- rows = self.get_visible_rows()
2954
- self.column_widths = get_column_widths(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)
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
- rows = self.get_visible_rows()
2981
- self.column_widths = get_column_widths(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)
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 : generate_picker_data(args.generate)
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
@@ -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["footer_string"][:footer_string_width]}"
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["footer_string"][:footer_string_width]}"
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["footer_string"][:footer_string_width]}"
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))
@@ -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.0
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 picker object to display rows:
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
- Use the listpick binary to generate and display rows based on a list of commands:
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