listpick 0.1.16.10__py3-none-any.whl → 0.1.16.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of listpick might be problematic. Click here for more details.

listpick/listpick_app.py CHANGED
@@ -20,7 +20,7 @@ import json
20
20
  import threading
21
21
  import string
22
22
  import logging
23
- from copy import copy
23
+ import copy
24
24
 
25
25
  from listpick.pane.pane_utils import get_file_attributes
26
26
  from listpick.pane.left_pane_functions import *
@@ -378,6 +378,11 @@ class Picker:
378
378
  self.getting_data.set()
379
379
 
380
380
  def __sizeof__(self):
381
+ """
382
+ Return the approximate memory footprint of the Picker instance.
383
+
384
+ This includes the size of the instance itself and the sizes of its attributes.
385
+ """
381
386
 
382
387
  size = super().__sizeof__()
383
388
 
@@ -388,7 +393,19 @@ class Picker:
388
393
  return size
389
394
 
390
395
  def set_config(self, path: str ="~/.config/listpick/config.toml") -> bool:
391
- """ Set config from toml file. """
396
+ """ Set config from toml file.
397
+
398
+ This method reads a configuration file in TOML format, applies settings
399
+ to the Picker, and returns a boolean indicating success or failure.
400
+
401
+ Args:
402
+ path (str): The path to the configuration file.
403
+
404
+ Returns:
405
+ bool: True if the configuration was successfully set; False otherwise.
406
+ """
407
+ self.logger.info(f"function: set_config()")
408
+
392
409
  path = os.path.expanduser(os.path.expandvars(path))
393
410
  if not os.path.exists(path):
394
411
  return False
@@ -397,7 +414,9 @@ class Picker:
397
414
  except Exception as e:
398
415
  self.logger.error(f"get_config({path}) load error. {e}")
399
416
  return False
417
+
400
418
 
419
+ # Change the global theme if colour_theme_number is in the loaded config
401
420
  if "general" in config:
402
421
  if "colour_theme_number" in config["general"] and config["general"]["colour_theme_number"] != self.colour_theme_number:
403
422
  global COLOURS_SET
@@ -405,7 +424,7 @@ class Picker:
405
424
  self.colours_end = set_colours(pick=config["general"]["colour_theme_number"], start=1)
406
425
  self.colours = get_colours(config["general"]["colour_theme_number"])
407
426
 
408
- self.logger.info(f"function: set_config()")
427
+ # load the rest of the config options
409
428
  if "general" in config:
410
429
  for key, val in config["general"].items():
411
430
  self.logger.info(f"set_config: key={key}, val={val}.")
@@ -417,30 +436,58 @@ class Picker:
417
436
  return True
418
437
 
419
438
  def get_config(self, path: str ="~/.config/listpick/config.toml") -> dict:
420
- """ Get config from file. """
439
+ """
440
+ Retrieve configuration settings from a specified TOML file.
441
+
442
+ Args:
443
+ path (str): The file path of the configuration file. Default is
444
+ ~/.config/listpick/config.toml.
445
+
446
+ Returns:
447
+ dict: A dictionary containing the configuration settings loaded
448
+ from the TOML file. In case of an error, an empty dictionary is returned.
449
+ """
450
+
421
451
  self.logger.info(f"function: get_config()")
422
452
  import toml
423
453
  with open(os.path.expanduser(path), "r") as f:
424
454
  config = toml.load(f)
425
455
  return config
426
456
 
427
- def update_term_size(self):
457
+ def update_term_size(self) -> None:
458
+ """
459
+ Update self.term_h, self.term_w the function provided to the Picker.
460
+
461
+ Returns:
462
+ None
463
+ """
428
464
  self.term_h, self.term_w = self.screen_size_function(self.stdscr)
429
465
  # self.term_h, self.term_w = self.stdscr.getmaxyx()
430
466
  # self.term_w, self.term_h = os.get_terminal_size()
431
467
 
432
468
 
433
- def get_term_size(self):
434
- return self.stdscr.getmaxyx()
435
- w, h = os.get_terminal_size()
436
- return h, w
469
+ def get_term_size(self) -> Tuple[int, int]:
470
+ """
471
+ Get the current terminal size using the function provided to the Picker.
472
+
473
+ Returns:
474
+ Tuple[int, int]: A tuple containing the (height, width) of the terminal.
475
+ """
476
+ return self.screen_size_function(self.stdscr)
477
+ # return self.stdscr.getmaxyx()
478
+ # w, h = os.get_terminal_size()
479
+ # return h, w
437
480
 
438
- def calculate_section_sizes(self):
481
+ def calculate_section_sizes(self) -> None:
439
482
  """
440
483
  Calculte the following for the Picker:
441
484
  self.items_per_page: the number of entry rows displayed
442
485
  self.bottom_space: the size of the footer + the bottom buffer space
443
486
  self.top_space: the size of the space at the top of the picker: title + modes + header + top_gap
487
+ Calculate and update the sizes of various sections of the Picker.
488
+
489
+ Returns:
490
+ None
444
491
  """
445
492
 
446
493
  self.logger.debug(f"function: calculate_section_sizes()")
@@ -512,16 +559,27 @@ class Picker:
512
559
 
513
560
 
514
561
  def get_visible_rows(self) -> list[list[str]]:
562
+ """
563
+ Calculate and return the currently visible rows based on the cursor position and pagination settings.
515
564
 
565
+ This method determines which rows from the indexed items are visible on the screen,
566
+ accounting for pagination and scrolling. It sets the starting and ending indices
567
+ based on the current cursor position and the number of items per page.
568
+
569
+ Returns:
570
+ list[list[str]]: The currently visible rows as a list of lists, where each inner
571
+ list represents a row of data. If there are no indexed items, it returns the
572
+ items array.
573
+ """
516
574
  self.logger.debug(f"function: get_visible_rows()")
517
575
  ## Scroll with column select
518
576
  if self.paginate:
519
- start_index = (self.cursor_pos//self.items_per_page) * self.items_per_page
577
+ start_index = (self.cursor_pos // self.items_per_page) * self.items_per_page
520
578
  end_index = min(start_index + self.items_per_page, len(self.indexed_items))
521
579
  ## Scroll
522
580
  else:
523
- scrolloff = self.items_per_page//2
524
- start_index = max(0, min(self.cursor_pos - (self.items_per_page-scrolloff), len(self.indexed_items)-self.items_per_page))
581
+ scrolloff = self.items_per_page // 2
582
+ start_index = max(0, min(self.cursor_pos - (self.items_per_page - scrolloff), len(self.indexed_items) - self.items_per_page))
525
583
  end_index = min(start_index + self.items_per_page, len(self.indexed_items))
526
584
  if len(self.indexed_items) == 0: start_index, end_index = 0, 0
527
585
 
@@ -534,13 +592,15 @@ class Picker:
534
592
  def initialise_picker_state(self, reset_colours=False) -> None:
535
593
  """ Initialise state variables for the picker. These are: debugging and colours. """
536
594
 
595
+ # Define global curses colours
537
596
  if curses.has_colors() and self.colours != None:
538
- # raise Exception("Terminal does not support color")
539
597
  curses.start_color()
598
+
540
599
  if reset_colours:
541
600
  global COLOURS_SET
542
601
  COLOURS_SET = False
543
602
  self.colours_end = set_colours(pick=self.colour_theme_number, start=self.colours_start)
603
+
544
604
  if curses.COLORS >= 255 and curses.COLOR_PAIRS >= 150:
545
605
  self.colours_start = self.colours_start
546
606
  self.notification_colours_start = self.colours_start+50
@@ -557,57 +617,32 @@ class Picker:
557
617
  self.colours = get_colours(self.colour_theme_number)
558
618
 
559
619
 
620
+ # Start logger
560
621
  debug_levels = [logging.DEBUG, logging.INFO, logging.WARNING, logging.ERROR, logging.CRITICAL]
561
622
  dbglvl = debug_levels[self.debug_level]
562
623
  self.logger = setup_logger(name="picker_log", log_file="picker.log", log_enabled=self.debug, level =dbglvl)
563
624
  self.logger.info(f"Initialiasing Picker.")
625
+
564
626
  self.update_term_size()
565
- # self.notification(self.stdscr, message=repr(self.logger))
566
-
567
-
568
- # 1 2 3 4 5
569
- # logger = logging.getLogger(__file__)
570
- # if self.debug_level == 0:
571
- # logger = logging.getLogger()
572
- # logger.disabled = True
573
- # else:
574
- #
575
- # file_handler = logging.FileHandler(f"{self.title}.log", mode='w')
576
- # formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s', '%m-%d-%Y %H:%M:%S')
577
- # file_handler.setFormatter(formatter)
578
- # logger.addHandler(file_handler)
579
- # logger.setLevel(debug_levels[self.debug_level-1])
580
-
581
- # logging.basicConfig(
582
- # level=debug_levels[self.debug_level-1],
583
- # format='%(asctime)s - %(levelname)s - %(message)s',
584
- # datefmt='%m-%d-%Y %H:%M:%S',
585
- # filename=f"{self.title}.log",
586
- # filemode="w",
587
- # )
588
- #
589
- # self.logger.info(f"Starging log. Log level {logger.getEffectiveLevel()}")
590
- # self.logger.info(f"Starging log. Log level {repr(debug_levels)}, {self.debug_level}, {debug_levels[self.debug_level-1]}")
591
- # self.notification(self.stdscr, f"Starging log. Log level {repr(debug_levels)}, {self.debug_level}, {debug_levels[self.debug_level-1]}")
592
- # self.notification(self.stdscr, f"{__file__}")
593
-
594
- ## Logging level plan
595
- # DEBUG: loop functions, draw screen, etc.
596
- # INFO: main functions
597
- # WARNING: any try-except fails
598
-
599
- # No set_escdelay function on windows.
627
+
628
+ # The curses implementation for some systems (e.g., windows) does not allow set_escdelay
600
629
  try:
601
630
  curses.set_escdelay(25)
602
631
  except:
603
632
  logging.warning("Error trying to set curses.set_escdelay")
604
633
 
605
- # self.stdscr.clear()
606
- # self.stdscr.refresh()
607
- # self.draw_screen()
608
-
609
634
  def initialise_variables(self, get_data: bool = False) -> None:
610
- """ Initialise the variables that keep track of the data. """
635
+ """
636
+ This method sets up the internal state of the Picker by initialising various attributes,
637
+ getting new data (if get_data is True), and ensuring that the lists used for tracking
638
+ selections, options, and items are correctly of the correct type, size, and shape. If
639
+ filter or sort queries are set then they are applied (or re-applied as the case may be).
640
+ The cursor_pos and selections are retained by tracking the id of the rows (where the id
641
+ is row[self.id_column]).
642
+
643
+ Parameters:
644
+ - get_data (bool): If True, pulls data synchronously and updates tracking variables.
645
+ """
611
646
 
612
647
  self.logger.info(f"function: initialise_variables()")
613
648
 
@@ -615,6 +650,7 @@ class Picker:
615
650
 
616
651
  ## Get data synchronously
617
652
  if get_data and self.refresh_function != None:
653
+ # Track cursor_pos and selections by ther id (row[self.id_column][col])
618
654
  if self.track_entries_upon_refresh and len(self.items) > 0:
619
655
  tracking = True
620
656
  selected_indices = get_selected_indices(self.selections)
@@ -622,34 +658,35 @@ class Picker:
622
658
  self.ids = [item[self.id_column] for i, item in enumerate(self.items) if i in selected_indices]
623
659
  self.ids_tuples = [(i, item[self.id_column]) for i, item in enumerate(self.items) if i in selected_indices]
624
660
 
625
- if len(self.indexed_items) > 0 and len(self.indexed_items) >= self.cursor_pos and len(self.indexed_items[0][1]) >= self.id_column:
661
+ if len(self.indexed_items) > 0 and self.cursor_pos < len(self.indexed_items) and len(self.indexed_items[0][1]) >= self.id_column:
626
662
  self.cursor_pos_id = self.indexed_items[self.cursor_pos][1][self.id_column]
627
663
  self.cursor_pos_prev = self.cursor_pos
628
-
629
-
630
664
 
665
+ # Set the state of the threading event
666
+ # Though we are getting data synchronously, we ensure the correct state for self.getting_data
631
667
  self.getting_data.clear()
632
668
  self.refresh_function(self.items, self.header, self.visible_rows_indices, self.getting_data)
633
669
 
634
- self.items = pad_lists_to_same_length(self.items)
635
670
 
671
+ # Ensure that an emtpy items object has the form [[]]
636
672
  if self.items == []: self.items = [[]]
637
- ## Ensure that items is a List[List[Str]] object
673
+
674
+ # Ensure that items is a List[List[Str]] object
638
675
  if len(self.items) > 0 and not isinstance(self.items[0], list):
639
676
  self.items = [[item] for item in self.items]
640
677
  # self.items = [[str(cell) for cell in row] for row in self.items]
641
678
 
679
+ # Ensure that the each of the rows of the items are of the same length
680
+ self.items = pad_lists_to_same_length(self.items)
642
681
 
643
682
  # Ensure that header is of the same length as the rows
644
683
  if self.header and len(self.items) > 0 and len(self.header) != len(self.items[0]):
645
684
  self.header = [str(self.header[i]) if i < len(self.header) else "" for i in range(len(self.items[0]))]
646
685
 
647
- # Constants
648
- # DEFAULT_ITEMS_PER_PAGE = os.get_terminal_size().lines - top_gap*2-2-int(bool(header))
649
-
650
686
  self.calculate_section_sizes()
651
687
 
652
- # Initial states
688
+
689
+ # Ensure that the selection-tracking variables are the correct shape
653
690
  if len(self.selections) != len(self.items):
654
691
  self.selections = {i : False if i not in self.selections else bool(self.selections[i]) for i in range(len(self.items))}
655
692
 
@@ -660,31 +697,34 @@ class Picker:
660
697
  self.cell_selections = {}
661
698
  self.selected_cells_by_row = {}
662
699
 
700
+ def extend_list_to_length(lst, length, default_value):
701
+ """Extend a list to the target length using a default value."""
702
+ if len(lst) < length:
703
+ lst.extend([copy.deepcopy(default_value) for _ in range(length - len(lst))])
663
704
 
664
705
 
665
- if len(self.require_option) < len(self.items):
666
- self.require_option += [self.require_option_default for i in range(len(self.items)-len(self.require_option))]
667
- if len(self.option_functions) < len(self.items):
668
- self.option_functions += [self.default_option_function for i in range(len(self.items)-len(self.option_functions))]
669
- if len(self.items)>0 and len(self.columns_sort_method) < len(self.items[0]):
670
- self.columns_sort_method = self.columns_sort_method + [0 for i in range(len(self.items[0])-len(self.columns_sort_method))]
671
- if len(self.items)>0 and len(self.sort_reverse) < len(self.items[0]):
672
- self.sort_reverse = self.sort_reverse + [False for i in range(len(self.items[0])-len(self.sort_reverse))]
673
- if len(self.items)>0 and len(self.editable_columns) < len(self.items[0]):
674
- self.editable_columns = self.editable_columns + [self.editable_by_default for i in range(len(self.items[0])-len(self.editable_columns))]
675
- if len(self.items)>0 and len(self.column_indices) < len(self.items[0]):
706
+ row_count = len(self.items)
707
+ col_count = len(self.items[0]) if row_count else 0
708
+
709
+ # Ensure that the length of the option lists are of the correct length.
710
+ if row_count > 0:
711
+ extend_list_to_length(self.require_option, length=row_count, default_value=self.require_option_default)
712
+ extend_list_to_length(self.option_functions, length=row_count, default_value=self.default_option_function)
713
+ extend_list_to_length(self.columns_sort_method, length=col_count, default_value=0)
714
+ extend_list_to_length(self.sort_reverse, length=col_count, default_value=False)
715
+ extend_list_to_length(self.editable_columns, length=col_count, default_value=self.editable_by_default)
716
+
717
+ if row_count > 0 and len(self.column_indices) < len(self.items[0]):
676
718
  self.column_indices = self.column_indices + [i for i in range(len(self.column_indices), len(self.items[0]))]
677
719
 
678
720
 
679
721
 
680
- # items2 = [[row[self.column_indices[i]] for i in range(len(row))] for row in self.items]
681
- # self.indexed_items = list(enumerate(items2))
722
+ # Create an indexed list of the items which will track the visible rows
682
723
  if self.items == [[]]: self.indexed_items = []
683
724
  else: self.indexed_items = list(enumerate(self.items))
684
725
 
685
- # If a filter is passed then refilter
726
+ # Apply the filter query
686
727
  if self.filter_query:
687
- # prev_index = self.indexed_items[cursor_pos][0] if len(self.indexed_items)>0 else 0
688
728
  # prev_index = self.indexed_items[cursor_pos][0] if len(self.indexed_items)>0 else 0
689
729
  self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
690
730
  if self.cursor_pos in [x[0] for x in self.indexed_items]: self.cursor_pos = [x[0] for x in self.indexed_items].index(self.cursor_pos)
@@ -700,22 +740,30 @@ class Picker:
700
740
  )
701
741
  if return_val:
702
742
  self.cursor_pos, self.search_index, self.search_count, self.highlights = tmp_cursor, tmp_index, tmp_count, tmp_highlights
703
- # If a sort is passed
743
+
744
+ # Apply the current sort method
704
745
  if len(self.indexed_items) > 0:
705
746
  sort_items(self.indexed_items, sort_method=self.columns_sort_method[self.sort_column], sort_column=self.sort_column, sort_reverse=self.sort_reverse[self.sort_column]) # Re-sort self.items based on new column
706
747
 
707
748
 
708
- # Adjust variables to ensure correctness if errors
709
- ## Move to a selectable row (if applicable)
749
+ # If we have more unselectable indices than rows, clear the unselectable_indices
710
750
  if len(self.items) <= len(self.unselectable_indices): self.unselectable_indices = []
711
- new_pos = (self.cursor_pos)%len(self.items)
712
- while new_pos in self.unselectable_indices and new_pos != self.cursor_pos:
713
- new_pos = (new_pos + 1) % len(self.items)
714
751
 
715
- assert new_pos < len(self.items)
716
- self.cursor_pos = new_pos
752
+ # Move cursur to a selectable row if we are currently on an unselectable row)
753
+ if self.cursor_pos * len(self.items) in self.unselectable_indices:
754
+ original_pos = new_pos = (self.cursor_pos)%len(self.items)
755
+ while new_pos in self.unselectable_indices:
756
+ new_pos = (new_pos + 1) % len(self.items)
757
+
758
+ # Break if we loop back to the original position
759
+ if new_pos == original_pos:
760
+ break
761
+
762
+ self.cursor_pos = max(0, min(new_pos, len(self.items)-1))
763
+
764
+ # Initialise sheets
765
+ extend_list_to_length(self.sheet_states, length=len(self.sheets), default_value={})
717
766
 
718
- # Sheets and files
719
767
  if len(self.sheet_states) < len(self.sheets):
720
768
  self.sheet_states += [{} for _ in range(len(self.sheets) - len(self.sheet_states))]
721
769
  if len(self.sheets):
@@ -723,15 +771,16 @@ class Picker:
723
771
  self.sheet_index = 0
724
772
  self.sheet_name = self.sheets[self.sheet_index]
725
773
 
726
- if len(self.loaded_file_states) < len(self.loaded_files):
727
- self.loaded_file_states += [{} for _ in range(len(self.loaded_files) - len(self.loaded_file_states))]
774
+ # Initialise files
775
+ extend_list_to_length(self.loaded_file_states, length=len(self.loaded_files), default_value={})
728
776
  if len(self.loaded_files):
729
777
  if self.loaded_file_index >= len(self.loaded_files):
730
778
  self.loaded_file_index = 0
731
779
  self.loaded_file = self.loaded_files[self.loaded_file_index]
732
780
 
733
- # if tracking and len(self.items) > 1:
734
- # Ensure that selected indices are tracked upon data refresh
781
+
782
+ # Ensure that the correct cursor_pos and selected indices are reselected
783
+ # if we have fetched new data.
735
784
  if self.track_entries_upon_refresh and (self.data_ready or tracking) and len(self.items) > 1:
736
785
  selected_indices = []
737
786
  all_ids = [item[self.id_column] for item in self.items]
@@ -754,6 +803,8 @@ class Picker:
754
803
 
755
804
 
756
805
 
806
+ # Ensure cursor_pos is set to a valid index
807
+ # If we have fetched new data then we attempt to set cursor_pos to the row with the same id as prev
757
808
  if len(self.indexed_items):
758
809
  if self.pin_cursor:
759
810
  self.cursor_pos = min(self.cursor_pos_prev, len(self.indexed_items)-1)
@@ -765,7 +816,12 @@ class Picker:
765
816
  else:
766
817
  self.cursor_pos = 0
767
818
 
768
- self.right_pane_index = max(0, min(self.right_pane_index, len(self.right_panes)-1))
819
+
820
+ # Ensure that the pane indices are within the range of the available panes.
821
+ if len(self.left_panes): self.left_pane_index %= len(self.left_panes)
822
+ else: self.left_pane_index = 0
823
+ if len(self.right_panes): self.right_pane_index %= len(self.right_panes)
824
+ else: self.right_pane_index = 0
769
825
 
770
826
 
771
827
 
@@ -862,14 +918,11 @@ class Picker:
862
918
  self.stdscr.erase()
863
919
 
864
920
  self.update_term_size()
865
- # if self.split_right and len(self.right_panes):
866
- # proportion = self.right_panes[self.right_pane_index]["proportion"]
867
- # self.rows_w, self.rows_h = int(self.term_w*proportion), self.term_h
868
- # else:
869
- # self.rows_w, self.rows_h = self.term_w, self.term_h
870
921
 
871
- # The height of the footer may need to be adjusted if the file changes.
922
+ # Determine footer size
872
923
  self.footer.adjust_sizes(self.term_h,self.term_w)
924
+
925
+ # The height of the footer may need to be adjusted if the file changes.
873
926
  self.calculate_section_sizes()
874
927
 
875
928
  # Test if the terminal is of a sufficient size to display the picker
@@ -887,28 +940,18 @@ class Picker:
887
940
  end_index = min(start_index + self.items_per_page, len(self.indexed_items))
888
941
  if len(self.indexed_items) == 0: start_index, end_index = 0, 0
889
942
 
890
- # self.column_widths = get_column_widths(self.items, header=self.header, max_column_width=self.max_column_width, number_columns=self.number_columns, max_total_width=w)
891
- # Determine widths based only on the currently indexed rows
892
- # rows = [v[1] for v in self.indexed_items] if len(self.indexed_items) else self.items
893
- # Determine widths based only on the currently displayed indexed rows
894
- # rows = [v[1] for v in self.indexed_items[start_index:end_index]] if len(self.indexed_items) else self.items
895
943
  self.get_visible_rows()
896
944
  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)
897
945
  visible_column_widths = [c for i,c in enumerate(self.column_widths) if i not in self.hidden_columns]
898
946
  visible_columns_total_width = sum(visible_column_widths) + len(self.separator)*(len(visible_column_widths)-1)
899
947
 
900
- # Determine the number of items_per_page, top_size and bottom_size
901
- # self.calculate_section_sizes()
902
-
903
- # top_space = self.top_gap
904
948
 
905
- ## Display title (if applicable)
949
+ ## Display title
906
950
  if self.title:
907
951
  padded_title = f" {self.title.strip()} "
908
952
  self.stdscr.addstr(self.top_gap, 0, f"{' ':^{self.term_w}}", curses.color_pair(self.colours_start+16))
909
953
  title_x = (self.term_w-wcswidth(padded_title))//2
910
954
  self.stdscr.addstr(self.top_gap, title_x, padded_title, curses.color_pair(self.colours_start+16) | curses.A_BOLD)
911
- # top_space += 1
912
955
 
913
956
  ## Display modes
914
957
  if self.display_modes and self.modes not in [[{}], []]:
@@ -930,7 +973,6 @@ class Picker:
930
973
  else:
931
974
  self.stdscr.addstr(self.top_gap+1, xmode, mode_str, curses.color_pair(self.colours_start+15) | curses.A_UNDERLINE)
932
975
  xmode += split_space+mode_widths[i]
933
- # top_space += 1
934
976
 
935
977
  ## Display header
936
978
  if self.header and self.show_header:
@@ -954,87 +996,66 @@ class Picker:
954
996
  header_str = header_str[self.leftmost_char:]
955
997
  header_str = header_str[:header_str_w]
956
998
  header_ypos = self.top_gap + bool(self.title) + bool(self.display_modes and self.modes)
999
+
1000
+ # Ensure that the full header width is filled--important if the header rows do not fill the terminal width
957
1001
  self.stdscr.addstr(header_ypos, self.rows_box_x_i, ' '*self.rows_w, curses.color_pair(self.colours_start+28) | curses.A_BOLD)
1002
+
1003
+ # Draw header string
958
1004
  self.stdscr.addstr(header_ypos, self.startx, header_str, curses.color_pair(self.colours_start+4) | curses.A_BOLD)
959
1005
 
960
1006
  # Highlight sort column
961
- try:
962
- if self.selected_column != None and self.selected_column not in self.hidden_columns:
963
- # start of string is on screen
964
- col_width = self.column_widths[self.selected_column]
965
- number = f"{self.selected_column}. " if self.number_columns else ""
966
- col_str = self.header[self.selected_column][:self.column_widths[self.selected_column]-len(number)]
967
- highlighted_col_str = (number+f"{col_str:^{self.column_widths[self.selected_column]-len(number)}}") + self.separator
968
-
969
- if len(self.column_widths) == 1:
970
- colour = curses.color_pair(self.colours_start+28) | curses.A_BOLD
971
- else:
972
- colour = curses.color_pair(self.colours_start+19) | curses.A_BOLD
973
- # Start of selected column is on the screen
974
- if self.leftmost_char <= len(up_to_selected_col) and self.leftmost_char+self.rows_w-self.left_gutter_width > len(up_to_selected_col):
975
- x_pos = len(up_to_selected_col) - self.leftmost_char + self.startx
1007
+ if self.selected_column != None and self.selected_column not in self.hidden_columns:
1008
+ # start of string is on screen
1009
+ col_width = self.column_widths[self.selected_column]
1010
+ number = f"{self.selected_column}. " if self.number_columns else ""
1011
+ col_str = self.header[self.selected_column][:self.column_widths[self.selected_column]-len(number)]
1012
+ highlighted_col_str = (number+f"{col_str:^{self.column_widths[self.selected_column]-len(number)}}") + self.separator
1013
+
1014
+ if len(self.column_widths) == 1:
1015
+ colour = curses.color_pair(self.colours_start+28) | curses.A_BOLD
1016
+ else:
1017
+ colour = curses.color_pair(self.colours_start+19) | curses.A_BOLD
1018
+ # Start of selected column is on the screen
1019
+ if self.leftmost_char <= len(up_to_selected_col) and self.leftmost_char+self.rows_w-self.left_gutter_width > len(up_to_selected_col):
1020
+ x_pos = len(up_to_selected_col) - self.leftmost_char + self.startx
976
1021
 
977
- # Whole cell of the selected column is on the screen
978
- if len(up_to_selected_col)+col_width - self.leftmost_char < self.rows_w-self.left_gutter_width:
979
- disp_str = highlighted_col_str
1022
+ # Whole cell of the selected column is on the screen
1023
+ if len(up_to_selected_col)+col_width - self.leftmost_char < self.rows_w-self.left_gutter_width:
1024
+ disp_str = highlighted_col_str
980
1025
 
981
- # Start of the cell is on the screen, but the end of the cell is not
982
- else:
983
- overflow = (len(up_to_selected_col)+len(highlighted_col_str)) - (self.leftmost_char+self.rows_w - self.left_gutter_width)
984
- disp_str = highlighted_col_str[:-overflow]
985
- disp_str_w = min(len(disp_str), self.term_w-x_pos)
986
- disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
987
-
988
- self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
989
- # Start of the cell is to the right of the screen
990
- elif self.leftmost_char+self.rows_w <= len(up_to_selected_col):
991
- pass
992
- # The end of the cell is on the screen, the start of the cell is not
993
- elif 0 <= len(up_to_selected_col)+col_width - self.leftmost_char <= self.rows_w :
994
- x_pos = self.startx
995
- beg = self.leftmost_char - len(up_to_selected_col)
996
- disp_str = highlighted_col_str[beg:]
997
- disp_str_w = min(len(disp_str), self.term_w-x_pos)
998
- disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
999
- self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
1000
- # The middle of the cell is on the screen, the start and end of the cell are not
1001
- elif self.leftmost_char <= len(up_to_selected_col) + col_width//2 <= self.leftmost_char+self.rows_w:
1002
- beg = self.leftmost_char - len(up_to_selected_col)
1003
- overflow = (len(up_to_selected_col)+len(highlighted_col_str)) - (self.leftmost_char+self.rows_w)
1004
- x_pos = self.startx
1005
- disp_str = highlighted_col_str[beg:-overflow]
1006
- disp_str_w = min(len(disp_str), self.term_w-x_pos)
1007
- disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
1008
-
1009
- self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
1010
- # The cell is to the left of the screen
1026
+ # Start of the cell is on the screen, but the end of the cell is not
1011
1027
  else:
1012
- pass
1013
-
1014
- # elif self.leftmost_char:
1015
- # os.system(f"notify-send 'cell is to the right of the screen'")
1016
-
1017
- #
1018
- #
1019
- # if len(self.header) > 1 and (len(up_to_selected_col)-self.leftmost_char) < self.rows_w:
1020
- # number = f"{self.selected_column}. " if self.number_columns else ""
1021
- # # number = f"{intStringToExponentString(self.selected_column)}. " if self.number_columns else ""
1022
- # # self.startx + len(up_to_selected_col) - self.leftmost_char
1023
- # highlighed_col_startx = max(self.startx, self.startx + len(up_to_selected_col) - self.leftmost_char)
1024
- #
1025
- #
1026
- # col_str = self.header[self.selected_column][:self.column_widths[self.selected_column]-len(number)]
1027
- # highlighted_col_str = (number+f"{col_str:^{self.column_widths[self.selected_column]}}") + self.separator
1028
- # end_of_highlighted_col_str = self.rows_w-(highlighed_col_startx+len(highlighted_col_str)) if (highlighed_col_startx+len(highlighted_col_str)) > self.rows_w else len(highlighted_col_str)
1029
- # if (highlighed_col_startx+len(highlighted_col_str)) > self.rows_w:
1030
- # end_of_highlighted_col_str = self.rows_w-(highlighed_col_startx+len(highlighted_col_str))
1031
- # else:
1032
- # end_of_highlighted_col_str = len(highlighted_col_str)
1033
- #
1034
- # start_of_highlighted_col_str = max(self.leftmost_char - len(up_to_selected_col), 0)
1035
- # self.stdscr.addstr(header_ypos, highlighed_col_startx , highlighted_col_str[start_of_highlighted_col_str:end_of_highlighted_col_str][:self.column_widths[self.selected_column]+len(self.separator)], curses.color_pair(self.colours_start+19) | curses.A_BOLD)
1036
- except:
1037
- pass
1028
+ overflow = (len(up_to_selected_col)+len(highlighted_col_str)) - (self.leftmost_char+self.rows_w - self.left_gutter_width)
1029
+ disp_str = highlighted_col_str[:-overflow]
1030
+ disp_str_w = min(len(disp_str), self.term_w-x_pos)
1031
+ disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
1032
+
1033
+ self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
1034
+ # Start of the cell is to the right of the screen
1035
+ elif self.leftmost_char+self.rows_w <= len(up_to_selected_col):
1036
+ pass
1037
+ # The end of the cell is on the screen, the start of the cell is not
1038
+ elif 0 <= len(up_to_selected_col)+col_width - self.leftmost_char <= self.rows_w :
1039
+ x_pos = self.startx
1040
+ beg = self.leftmost_char - len(up_to_selected_col)
1041
+ disp_str = highlighted_col_str[beg:]
1042
+ disp_str_w = min(len(disp_str), self.term_w-x_pos)
1043
+ disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
1044
+ self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
1045
+ # The middle of the cell is on the screen, the start and end of the cell are not
1046
+ elif self.leftmost_char <= len(up_to_selected_col) + col_width//2 <= self.leftmost_char+self.rows_w:
1047
+ beg = self.leftmost_char - len(up_to_selected_col)
1048
+ overflow = (len(up_to_selected_col)+len(highlighted_col_str)) - (self.leftmost_char+self.rows_w)
1049
+ x_pos = self.startx
1050
+ disp_str = highlighted_col_str[beg:-overflow]
1051
+ disp_str_w = min(len(disp_str), self.term_w-x_pos)
1052
+ disp_str = truncate_to_display_width(disp_str, disp_str_w, self.centre_in_cols, self.unicode_char_width)
1053
+
1054
+ self.stdscr.addstr(header_ypos, x_pos , disp_str, colour)
1055
+
1056
+ # The cell is to the left of the focused part of the screen
1057
+ else:
1058
+ pass
1038
1059
 
1039
1060
  # Display row header
1040
1061
  if self.show_row_header:
@@ -1058,41 +1079,34 @@ class Picker:
1058
1079
  colour = curses.color_pair(self.colours_start+colour_pair_number) | curses.A_BOLD
1059
1080
  else:
1060
1081
  colour = curses.color_pair(self.colours_start+colour_pair_number)
1061
- try:
1062
- # Start of cell is on screen
1063
- if self.startx <= cell_pos <= self.rows_w+self.startx:
1064
- s = "max" if cell_max_width <= cell_width else "norm"
1065
- self.stdscr.addstr(y, cell_pos, (' '*cell_width)[:cell_max_width], colour)
1066
- if self.centre_in_cols:
1067
- cell_value = f"{self.indexed_items[row][1][col]:^{cell_width-len(self.separator)}}" + self.separator
1068
- else:
1069
- cell_value = self.indexed_items[row][1][col][:self.column_widths[col]] + self.separator
1070
- # cell_value = cell_value[:min(cell_width, cell_max_width)-len(self.separator)]
1071
- cell_value = truncate_to_display_width(cell_value, min(cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
1072
- # cell_value = cell_value + self.separator
1073
- # cell_value = cell_value
1074
- cell_value = truncate_to_display_width(cell_value, min(cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
1075
- if wcswidth(cell_value) + cell_pos > self.term_w:
1076
- cell_value = truncate_to_display_width(cell_value, self.term_w-cell_pos-10, self.centre_in_cols, self.unicode_char_width)
1077
-
1078
- self.stdscr.addstr(y, cell_pos, cell_value, colour)
1079
- # Part of the cell is on screen
1080
- elif self.startx <= cell_pos+cell_width and cell_pos <= (self.rows_w):
1081
- s = "max" if cell_max_width <= cell_width else "norm"
1082
- cell_start = self.startx - cell_pos
1083
- # self.stdscr.addstr(y, self.startx, ' '*(cell_width-cell_start), curses.color_pair(self.colours_start+colour_pair_number))
1084
- cell_value = self.indexed_items[row][1][col]
1085
- cell_value = f"{cell_value:^{self.column_widths[col]}}"
1086
-
1087
- cell_value = cell_value[cell_start:visible_column_widths[col]][:self.rows_w-self.left_gutter_width]
1088
- cell_value = truncate_to_display_width(cell_value, min(wcswidth(cell_value), cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
1089
- cell_value += self.separator
1090
- cell_value = truncate_to_display_width(cell_value, min(wcswidth(cell_value), cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
1091
- self.stdscr.addstr(y, self.startx, cell_value, colour)
1082
+ # Start of cell is on screen
1083
+ if self.startx <= cell_pos <= self.rows_w+self.startx:
1084
+ s = "max" if cell_max_width <= cell_width else "norm"
1085
+ self.stdscr.addstr(y, cell_pos, (' '*cell_width)[:cell_max_width], colour)
1086
+ if self.centre_in_cols:
1087
+ cell_value = f"{self.indexed_items[row][1][col]:^{cell_width-len(self.separator)}}" + self.separator
1092
1088
  else:
1093
- pass
1094
- # if colour_pair_number == 5:
1095
- except:
1089
+ cell_value = self.indexed_items[row][1][col][:self.column_widths[col]] + self.separator
1090
+ cell_value = truncate_to_display_width(cell_value, min(cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
1091
+ cell_value = truncate_to_display_width(cell_value, min(cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
1092
+ if wcswidth(cell_value) + cell_pos > self.term_w:
1093
+ cell_value = truncate_to_display_width(cell_value, self.term_w-cell_pos-10, self.centre_in_cols, self.unicode_char_width)
1094
+
1095
+ self.stdscr.addstr(y, cell_pos, cell_value, colour)
1096
+
1097
+ # Part of the cell is on screen
1098
+ elif self.startx <= cell_pos+cell_width and cell_pos <= (self.rows_w):
1099
+ s = "max" if cell_max_width <= cell_width else "norm"
1100
+ cell_start = self.startx - cell_pos
1101
+ cell_value = self.indexed_items[row][1][col]
1102
+ cell_value = f"{cell_value:^{self.column_widths[col]}}"
1103
+
1104
+ cell_value = cell_value[cell_start:visible_column_widths[col]][:self.rows_w-self.left_gutter_width]
1105
+ cell_value = truncate_to_display_width(cell_value, min(wcswidth(cell_value), cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
1106
+ cell_value += self.separator
1107
+ cell_value = truncate_to_display_width(cell_value, min(wcswidth(cell_value), cell_width, cell_max_width), self.centre_in_cols, self.unicode_char_width)
1108
+ self.stdscr.addstr(y, self.startx, cell_value, colour)
1109
+ else:
1096
1110
  pass
1097
1111
 
1098
1112
 
listpick/ui/keys.py CHANGED
@@ -51,7 +51,7 @@ picker_keys = {
51
51
  "continue_search_forward": [ord('n')],
52
52
  "continue_search_backward": [ord('N')],
53
53
  "cancel": [27], # Escape key
54
- "opts_input": [ord('e')],
54
+ "opts_input": [keycodes.META_o],
55
55
  "opts_select": [ord('o')],
56
56
  "mode_next": [9], # Tab key
57
57
  "mode_prev": [353], # Shift+Tab key
@@ -294,6 +294,61 @@ def get_colours(pick:int=0) -> Dict[str, int]:
294
294
  'active_column_bg': curses.COLOR_BLACK,
295
295
  'active_column_fg': curses.COLOR_WHITE,
296
296
  },
297
+ ### (6) Use default colors for bg
298
+ # {
299
+ # 'background': -1,
300
+ # 'normal_fg': -1,
301
+ # 'unselected_bg': -1,
302
+ # 'unselected_fg': -1,
303
+ # 'cursor_bg': 21,
304
+ # 'cursor_fg': -1,
305
+ # 'selected_bg': 54,
306
+ # 'selected_fg': -1,
307
+ # 'header_bg': 255,
308
+ # 'header_fg': 232,
309
+ # 'error_bg': 232,
310
+ # 'error_fg': curses.COLOR_RED,
311
+ # 'complete_bg': 232,
312
+ # 'complete_fg': 82,
313
+ # 'waiting_bg': 232,
314
+ # 'waiting_fg': curses.COLOR_YELLOW,
315
+ # 'active_bg': 232,
316
+ # 'active_fg': 33,
317
+ # 'paused_bg': -1,
318
+ # 'paused_fg': 244,
319
+ # 'search_bg': 162,
320
+ # 'search_fg': -1,
321
+ # 'active_input_bg': -1,
322
+ # 'active_input_fg': 232,
323
+ # 'modes_selected_bg': 232,
324
+ # 'modes_selected_fg': -1,
325
+ # 'modes_unselected_bg': 255,
326
+ # 'modes_unselected_fg': 232,
327
+ # 'title_bar': 232,
328
+ # 'title_bg': 232,
329
+ # 'title_fg': -1,
330
+ # 'scroll_bar_bg': 247,
331
+ # 'selected_header_column_bg': 232,
332
+ # 'selected_header_column_fg': -1,
333
+ # 'unselected_header_column_bg': -1,
334
+ # 'unselected_header_column_fg': -1,
335
+ # 'footer_bg': 232,
336
+ # 'footer_fg': -1,
337
+ # 'refreshing_bg': 232,
338
+ # 'refreshing_fg': -1,
339
+ # 'refreshing_inactive_bg': 232,
340
+ # 'refreshing_inactive_fg': 232,
341
+ # '40pc_bg': 232,
342
+ # '40pc_fg': 166,
343
+ # 'footer_string_bg': 232,
344
+ # 'footer_string_fg': -1,
345
+ # 'selected_cell_bg': 54,
346
+ # 'selected_cell_fg': -1,
347
+ # 'deselecting_cell_bg': 162,
348
+ # 'deselecting_cell_fg': -1,
349
+ # 'active_column_bg': 234,
350
+ # 'active_column_fg': -1,
351
+ # },
297
352
  ]
298
353
  for colour in colours:
299
354
  colour["20pc_bg"] = colour["background"]
listpick/utils/utils.py CHANGED
@@ -15,6 +15,7 @@ import os
15
15
  from typing import Tuple, Dict
16
16
  import logging
17
17
  import shlex
18
+ from collections import defaultdict
18
19
 
19
20
  logger = logging.getLogger('picker_log')
20
21
 
@@ -205,17 +206,11 @@ def get_selected_cells(cell_selections: Dict[Tuple[int, int], bool]) -> list[Tup
205
206
  def get_selected_cells_by_row(cell_selections: dict[tuple[int, int], bool]) -> dict[int, list[int]]:
206
207
  """ {0: [1,2], 9: [1] }"""
207
208
 
208
- d = {}
209
- try:
210
- for tup in cell_selections.keys():
211
- if cell_selections[tup]:
212
- if tup[0] in d:
213
- d[tup[0]].append(tup[1])
214
- else:
215
- d[tup[0]] = [tup[1]]
216
- except:
217
- pass
218
- return d
209
+ d = defaultdict(list)
210
+ for (row, col), selected in cell_selections.items():
211
+ if selected:
212
+ d[row].append(col)
213
+ return dict(d)
219
214
 
220
215
  def get_selected_values(items: list[list[str]], selections: dict[int, bool]) -> list[list[str]]:
221
216
  """ Return a list of rows based on wich are True in the selections dictionary. """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: listpick
3
- Version: 0.1.16.10
3
+ Version: 0.1.16.11
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
@@ -1,6 +1,6 @@
1
1
  listpick/__init__.py,sha256=ExXc97-bibodH--wlwpQivl0zCNR5D1hvpvrf7OBofU,154
2
2
  listpick/__main__.py,sha256=wkCjDdqw093W27yWwnlC3nG_sMRKaIad7hHHWy0RBgY,193
3
- listpick/listpick_app.py,sha256=X8PYkBDVhP137M9w7IQAMz5A9dowh4p6bpWHRj8SNfM,213114
3
+ listpick/listpick_app.py,sha256=IavN099zeGWv36ogNjbR20vHXtvRYR74-pZfqiSFHng,211476
4
4
  listpick/pane/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  listpick/pane/get_data.py,sha256=l12mHIb6qoZWIfW5zZZY8K8EqNcyIcRiHgtRaM2CVGs,2735
6
6
  listpick/pane/left_pane_functions.py,sha256=SVIW4Ef8uNPBQRk4hL67mEFL3pfgChSFZSMRz06CVzw,5543
@@ -13,8 +13,8 @@ listpick/ui/draw_screen.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  listpick/ui/footer.py,sha256=NcdH1uO_ma91m0qCczyQZ3zGrexfkiEnwDf5E4tHSMk,15089
14
14
  listpick/ui/help_screen.py,sha256=zbfGIgb-IXtATpl4_Sx7nPbsnRXZ7eiMYlCKGS9EFmw,5608
15
15
  listpick/ui/input_field.py,sha256=scJjvmSS0QqeDbCky7_0Zgt35Aki7gezRJkrQROlLg4,30034
16
- listpick/ui/keys.py,sha256=FlpdWqX1lTP7K6gqHn9jfH2MmuloigeOA7EMzYhaJV8,13845
17
- listpick/ui/picker_colours.py,sha256=FFsyny_q0mGO6u7B1n7anuReBtP7Jw6LrgX5ycN-MRM,13413
16
+ listpick/ui/keys.py,sha256=5wXx7K4zLsKw3yt_Q9_GCpgtcGMvQyChXrBhXtuc8-8,13852
17
+ listpick/ui/picker_colours.py,sha256=PYWtJEbBCsiM-Gbm83vJiFoIwfKuJuP4SGKxpozszKY,15159
18
18
  listpick/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  listpick/utils/clipboard_operations.py,sha256=ORdNm2kgGbfs51xJSvgJPERgoSmBgT11axuMkvSoP9A,3133
20
20
  listpick/utils/config.py,sha256=MEnAZg2Rhfl38XofEIN0uoVAOY7I0ftc79Evk3fOiVw,1654
@@ -32,10 +32,10 @@ listpick/utils/searching.py,sha256=Xk5UIqamNHL2L90z3ACB_Giqdpi9iRKoAJ6pKaqaD7Q,3
32
32
  listpick/utils/sorting.py,sha256=WZZiVlVA3Zkcpwji3U5SNFlQ14zVEk3cZJtQirBkecQ,5329
33
33
  listpick/utils/table_to_list_of_lists.py,sha256=XBj7eGBDF15BRME-swnoXyOfZWxXCxrXp0pzsBfcJ5g,12224
34
34
  listpick/utils/user_input.py,sha256=L3ylI7nnuFM_TP1XKwpiKpxUSkNb2W5cr7mJjTmv_6E,4582
35
- listpick/utils/utils.py,sha256=nsR6orCBQy3rTXrCweq8cV-RzRVU15v3J9NclPeAOJk,13741
36
- listpick-0.1.16.10.dist-info/licenses/LICENSE.txt,sha256=2mP-MRHJptADDNE9VInMNg1tE-C6Qv93Z4CCQKrpg9w,1061
37
- listpick-0.1.16.10.dist-info/METADATA,sha256=g830mNna2Vn1PxyeN5DBSgWm8CQP0CPU4le48laOd48,8025
38
- listpick-0.1.16.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
39
- listpick-0.1.16.10.dist-info/entry_points.txt,sha256=-QCf_BKIkUz35Y9nkYpjZWs2Qg0KfRna2PAs5DnF6BE,43
40
- listpick-0.1.16.10.dist-info/top_level.txt,sha256=5mtsGEz86rz3qQDe0D463gGjAfSp6A3EWg4J4AGYr-Q,9
41
- listpick-0.1.16.10.dist-info/RECORD,,
35
+ listpick/utils/utils.py,sha256=_Z3pGDMPnEgPuhl_1BpEtXYi5tfWbuwRitj8Y5TeRUg,13653
36
+ listpick-0.1.16.11.dist-info/licenses/LICENSE.txt,sha256=2mP-MRHJptADDNE9VInMNg1tE-C6Qv93Z4CCQKrpg9w,1061
37
+ listpick-0.1.16.11.dist-info/METADATA,sha256=WRPPZnTIxabHjUTi34ZG_CjXad6aDbMqoO-V_7z7eQk,8025
38
+ listpick-0.1.16.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
39
+ listpick-0.1.16.11.dist-info/entry_points.txt,sha256=-QCf_BKIkUz35Y9nkYpjZWs2Qg0KfRna2PAs5DnF6BE,43
40
+ listpick-0.1.16.11.dist-info/top_level.txt,sha256=5mtsGEz86rz3qQDe0D463gGjAfSp6A3EWg4J4AGYr-Q,9
41
+ listpick-0.1.16.11.dist-info/RECORD,,