listpick 0.1.13.60__tar.gz → 0.1.13.62__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.
- {listpick-0.1.13.60 → listpick-0.1.13.62}/CHANGELOG.md +2 -0
- {listpick-0.1.13.60/src/listpick.egg-info → listpick-0.1.13.62}/PKG-INFO +5 -5
- {listpick-0.1.13.60 → listpick-0.1.13.62}/README.md +4 -4
- {listpick-0.1.13.60 → listpick-0.1.13.62}/setup.py +1 -1
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/listpick_app.py +37 -12
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/utils/generate_data.py +4 -1
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/utils/utils.py +19 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62/src/listpick.egg-info}/PKG-INFO +5 -5
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick.egg-info/SOURCES.txt +0 -10
- listpick-0.1.13.60/examples/footer_string_example.py +0 -21
- listpick-0.1.13.60/examples/input.toml +0 -25
- listpick-0.1.13.60/examples/input.txt +0 -20
- listpick-0.1.13.60/examples/list_files.toml +0 -15
- listpick-0.1.13.60/examples/list_files_empty.toml +0 -15
- listpick-0.1.13.60/examples/picker_example.py +0 -45
- listpick-0.1.13.60/examples/setup.cfg +0 -26
- listpick-0.1.13.60/examples/template.py +0 -19
- listpick-0.1.13.60/examples/video_duplicates.toml +0 -27
- listpick-0.1.13.60/src/listpick/ui/footer_1.py +0 -202
- {listpick-0.1.13.60 → listpick-0.1.13.62}/.gitignore +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/LICENSE.txt +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/TODO.md +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/assets/aria2tui_screenshot.png +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/assets/file_compare.png +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/assets/lpfman.png +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/listpick.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/requirements.txt +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/setup.cfg +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/__init__.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/__main__.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/ui/__init__.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/ui/build_help.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/ui/footer.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/ui/help_screen.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/ui/input_field.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/ui/keys.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/ui/pane_stuff.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/ui/picker_colours.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/utils/__init__.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/utils/clipboard_operations.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/utils/config.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/utils/dump.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/utils/filtering.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/utils/options_selectors.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/utils/paste_operations.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/utils/picker_log.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/utils/search_and_filter_utils.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/utils/searching.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/utils/sorting.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick/utils/table_to_list_of_lists.py +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick.egg-info/dependency_links.txt +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick.egg-info/entry_points.txt +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick.egg-info/requires.txt +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/src/listpick.egg-info/top_level.txt +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/tests/kitty_control.sh +0 -0
- {listpick-0.1.13.60 → listpick-0.1.13.62}/tests/sorting_dates.csv +0 -0
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
- Speed improvements:
|
|
30
30
|
- Create and track self.selected_cells_by_row when selections change rather than derive it from the self.cell_selections
|
|
31
31
|
- Much faster with very large data sets as we need to determine selected_cells_by_row every time we run self.draw_screen()
|
|
32
|
+
- We can now pipe data from cells in multiple columns to a command.
|
|
33
|
+
- e.g., pipe two cols to gnuplot
|
|
32
34
|
|
|
33
35
|
## [0.1.13] 2025-07-28
|
|
34
36
|
- Cell-based picker is now supported.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: listpick
|
|
3
|
-
Version: 0.1.13.
|
|
3
|
+
Version: 0.1.13.62
|
|
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
|
|
@@ -80,7 +80,7 @@ close_curses(stdscr)
|
|
|
80
80
|
Use the listpick binary to generate and display rows based on a list of commands:
|
|
81
81
|
|
|
82
82
|
```
|
|
83
|
-
wget https://raw.githubusercontent.com/grimandgreedy/listpick/refs/heads/master/examples/list_files.toml
|
|
83
|
+
wget https://raw.githubusercontent.com/grimandgreedy/listpick/refs/heads/master/examples/data_generation/list_files.toml
|
|
84
84
|
listpick -g list_files.py
|
|
85
85
|
```
|
|
86
86
|
|
|
@@ -100,9 +100,9 @@ The application allows you to:
|
|
|
100
100
|
## Examples
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
### Identify video duplicates (./examples/video_duplicates.toml):
|
|
103
|
+
### Identify video duplicates (./examples/data_generation//video_duplicates.toml):
|
|
104
104
|
```python
|
|
105
|
-
listpick -g ./examples/video_duplicates.toml
|
|
105
|
+
listpick -g ./examples/data_generation/video_duplicates.toml
|
|
106
106
|
```
|
|
107
107
|
- From the list of commands in the toml file we generate the properties we will use to identify the duplicates.
|
|
108
108
|
|
|
@@ -142,7 +142,7 @@ listpick -i ~/dn.pkl -t pkl
|
|
|
142
142
|
|
|
143
143
|
2. **Generate data based on an toml file with relevant commands to generate the rows.**
|
|
144
144
|
```python
|
|
145
|
-
listpick -g ./examples/video_duplicates.toml
|
|
145
|
+
listpick -g ./examples/data_generation/video_duplicates.toml
|
|
146
146
|
```
|
|
147
147
|
|
|
148
148
|
- See ./examples/
|
|
@@ -39,7 +39,7 @@ close_curses(stdscr)
|
|
|
39
39
|
Use the listpick binary to generate and display rows based on a list of commands:
|
|
40
40
|
|
|
41
41
|
```
|
|
42
|
-
wget https://raw.githubusercontent.com/grimandgreedy/listpick/refs/heads/master/examples/list_files.toml
|
|
42
|
+
wget https://raw.githubusercontent.com/grimandgreedy/listpick/refs/heads/master/examples/data_generation/list_files.toml
|
|
43
43
|
listpick -g list_files.py
|
|
44
44
|
```
|
|
45
45
|
|
|
@@ -59,9 +59,9 @@ The application allows you to:
|
|
|
59
59
|
## Examples
|
|
60
60
|
|
|
61
61
|
|
|
62
|
-
### Identify video duplicates (./examples/video_duplicates.toml):
|
|
62
|
+
### Identify video duplicates (./examples/data_generation//video_duplicates.toml):
|
|
63
63
|
```python
|
|
64
|
-
listpick -g ./examples/video_duplicates.toml
|
|
64
|
+
listpick -g ./examples/data_generation/video_duplicates.toml
|
|
65
65
|
```
|
|
66
66
|
- From the list of commands in the toml file we generate the properties we will use to identify the duplicates.
|
|
67
67
|
|
|
@@ -101,7 +101,7 @@ listpick -i ~/dn.pkl -t pkl
|
|
|
101
101
|
|
|
102
102
|
2. **Generate data based on an toml file with relevant commands to generate the rows.**
|
|
103
103
|
```python
|
|
104
|
-
listpick -g ./examples/video_duplicates.toml
|
|
104
|
+
listpick -g ./examples/data_generation/video_duplicates.toml
|
|
105
105
|
```
|
|
106
106
|
|
|
107
107
|
- See ./examples/
|
|
@@ -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.13.
|
|
19
|
+
version = "0.1.13.62",
|
|
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.",
|
|
@@ -449,8 +449,6 @@ class Picker:
|
|
|
449
449
|
# self.stdscr.refresh()
|
|
450
450
|
# self.draw_screen(self.indexed_items, self.highlights)
|
|
451
451
|
|
|
452
|
-
|
|
453
|
-
|
|
454
452
|
def initialise_variables(self, get_data: bool = False) -> None:
|
|
455
453
|
""" Initialise the variables that keep track of the data. """
|
|
456
454
|
|
|
@@ -473,6 +471,7 @@ class Picker:
|
|
|
473
471
|
|
|
474
472
|
self.items, self.header = self.refresh_function()
|
|
475
473
|
|
|
474
|
+
self.items = pad_lists_to_same_length(self.items)
|
|
476
475
|
|
|
477
476
|
if self.items == []: self.items = [[]]
|
|
478
477
|
## Ensure that items is a List[List[Str]] object
|
|
@@ -1261,7 +1260,12 @@ class Picker:
|
|
|
1261
1260
|
message_width = notification_width-5
|
|
1262
1261
|
|
|
1263
1262
|
if not message: message = "!!"
|
|
1264
|
-
|
|
1263
|
+
if type(message) == type(""):
|
|
1264
|
+
mw = message_width
|
|
1265
|
+
submenu_items = [[message[i*mw:(i+1)*mw]] for i in range(len(message)//mw+1)]
|
|
1266
|
+
elif type(message) != type([]):
|
|
1267
|
+
submenu_items = [[" !!"]]
|
|
1268
|
+
|
|
1265
1269
|
|
|
1266
1270
|
notification_remap_keys = {
|
|
1267
1271
|
curses.KEY_RESIZE: curses.KEY_F5,
|
|
@@ -1959,7 +1963,8 @@ class Picker:
|
|
|
1959
1963
|
|
|
1960
1964
|
while True:
|
|
1961
1965
|
key = self.stdscr.getch()
|
|
1962
|
-
|
|
1966
|
+
if key:
|
|
1967
|
+
self.logger.info(f"key={key}")
|
|
1963
1968
|
h, w = self.stdscr.getmaxyx()
|
|
1964
1969
|
if key in self.disabled_keys: continue
|
|
1965
1970
|
clear_screen=True
|
|
@@ -2760,26 +2765,46 @@ class Picker:
|
|
|
2760
2765
|
if not selected_indices:
|
|
2761
2766
|
selected_indices = [self.indexed_items[self.cursor_pos][0]]
|
|
2762
2767
|
|
|
2763
|
-
full_values = [format_row_full(self.items[i], self.hidden_columns) for i in selected_indices] # Use format_row_full for full data
|
|
2764
|
-
|
|
2768
|
+
# full_values = [format_row_full(self.items[i], self.hidden_columns) for i in selected_indices] # Use format_row_full for full data
|
|
2769
|
+
if self.cell_cursor:
|
|
2770
|
+
|
|
2771
|
+
full_values = []
|
|
2772
|
+
for row in self.selected_cells_by_row.keys():
|
|
2773
|
+
selected_cell_row_str = ""
|
|
2774
|
+
for cell in self.selected_cells_by_row[row]:
|
|
2775
|
+
if " " in self.items[row][cell]:
|
|
2776
|
+
selected_cell_row_str += repr(self.items[row][cell])
|
|
2777
|
+
else:
|
|
2778
|
+
selected_cell_row_str += self.items[row][cell]
|
|
2779
|
+
selected_cell_row_str += "\t"
|
|
2780
|
+
full_values.append(selected_cell_row_str.strip())
|
|
2781
|
+
|
|
2782
|
+
|
|
2783
|
+
# full_values = ["\t".join([repr(self.items[key][cell]) for cell in self.selected_cells_by_row[key]]) for key in self.selected_cells_by_row.keys()]
|
|
2784
|
+
# full_values = ["\t".join([self.items[key][cell] for cell in self.selected_cells_by_row[key]]) for key in self.selected_cells_by_row.keys()]
|
|
2785
|
+
else:
|
|
2786
|
+
full_values = [self.items[i][self.selected_column] for i in selected_indices]
|
|
2765
2787
|
if full_values:
|
|
2766
|
-
command = usrtxt.split()
|
|
2788
|
+
# command = usrtxt.split()
|
|
2789
|
+
command = usrtxt
|
|
2767
2790
|
# command = ['xargs', '-d' , '"\n"' '-I', '{}', 'mpv', '{}']
|
|
2768
2791
|
# command = ['xargs', '-d' , '"\n"' '-I', '{}', 'mpv', '{}']
|
|
2769
2792
|
# command = "xargs -d '\n' -I{} mpv {}"
|
|
2770
2793
|
|
|
2771
2794
|
try:
|
|
2772
|
-
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
2795
|
+
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True)
|
|
2773
2796
|
|
|
2774
2797
|
if process.stdin != None:
|
|
2775
2798
|
for value in full_values:
|
|
2776
|
-
process.stdin.write((
|
|
2799
|
+
process.stdin.write((value + '\n').encode())
|
|
2800
|
+
# process.stdin.write((value + '\n').encode())
|
|
2777
2801
|
|
|
2778
2802
|
process.stdin.close()
|
|
2779
2803
|
|
|
2780
2804
|
self.notification(self.stdscr, message=f"{len(full_values)} strings piped to {repr(usrtxt)}")
|
|
2781
2805
|
except Exception as e:
|
|
2782
2806
|
self.notification(self.stdscr, message=f"{e}")
|
|
2807
|
+
# self.notification(self.stdscr, message=f"Error: {str(e)}")
|
|
2783
2808
|
|
|
2784
2809
|
|
|
2785
2810
|
elif self.check_key("open", key, self.keys_dict):
|
|
@@ -3158,9 +3183,9 @@ def main() -> None:
|
|
|
3158
3183
|
function_data["centre_in_terminal_vertical"] = True
|
|
3159
3184
|
function_data["highlight_full_row"] = True
|
|
3160
3185
|
function_data["pin_cursor"] = True
|
|
3161
|
-
function_data["display_infobox"] = True
|
|
3162
|
-
function_data["infobox_items"] = [["1"], ["2"], ["3"]]
|
|
3163
|
-
function_data["infobox_title"] = "Title"
|
|
3186
|
+
# function_data["display_infobox"] = True
|
|
3187
|
+
# function_data["infobox_items"] = [["1"], ["2"], ["3"]]
|
|
3188
|
+
# function_data["infobox_title"] = "Title"
|
|
3164
3189
|
# function_data["footer_string"] = "Title"
|
|
3165
3190
|
function_data["highlights"] = highlights
|
|
3166
3191
|
function_data["show_footer"] = False
|
|
@@ -25,7 +25,10 @@ def generate_columns(funcs: list, files: list) -> list[list[str]]:
|
|
|
25
25
|
for file in files:
|
|
26
26
|
item = []
|
|
27
27
|
for func in funcs:
|
|
28
|
-
|
|
28
|
+
try:
|
|
29
|
+
item.append(func(file))
|
|
30
|
+
except:
|
|
31
|
+
item.append("")
|
|
29
32
|
items.append(item)
|
|
30
33
|
|
|
31
34
|
return items
|
|
@@ -344,3 +344,22 @@ def guess_file_type(filename: str) -> str:
|
|
|
344
344
|
""" Guess filetype. Currently just uses the extension of the file. """
|
|
345
345
|
logger.info("function: guess_file_type (utils.py)")
|
|
346
346
|
return filename.split(".")[-1]
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def pad_lists_to_same_length(list_of_lists: list[list[str]]) -> list[list[str]]:
|
|
350
|
+
""" Ensure that all lists in a list of lists are of the same length. Pad any shorter sublists with empty strings. """
|
|
351
|
+
if not list_of_lists or list_of_lists in [[], [[]]]:
|
|
352
|
+
return []
|
|
353
|
+
if type(list_of_lists) == type([]) and len(list_of_lists) and type(list_of_lists[0]) == type(""):
|
|
354
|
+
list_of_lists = [[x] for x in list_of_lists]
|
|
355
|
+
|
|
356
|
+
# Find the maximum length of the sublists
|
|
357
|
+
lengths = [len(sublist) for sublist in list_of_lists]
|
|
358
|
+
max_length = max(lengths)
|
|
359
|
+
min_length = min(lengths)
|
|
360
|
+
if min_length == max_length: return list_of_lists
|
|
361
|
+
|
|
362
|
+
# Pad each sublist with empty strings to match the maximum length
|
|
363
|
+
padded_list = [sublist + [''] * (max_length - len(sublist)) for sublist in list_of_lists]
|
|
364
|
+
|
|
365
|
+
return padded_list
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: listpick
|
|
3
|
-
Version: 0.1.13.
|
|
3
|
+
Version: 0.1.13.62
|
|
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
|
|
@@ -80,7 +80,7 @@ close_curses(stdscr)
|
|
|
80
80
|
Use the listpick binary to generate and display rows based on a list of commands:
|
|
81
81
|
|
|
82
82
|
```
|
|
83
|
-
wget https://raw.githubusercontent.com/grimandgreedy/listpick/refs/heads/master/examples/list_files.toml
|
|
83
|
+
wget https://raw.githubusercontent.com/grimandgreedy/listpick/refs/heads/master/examples/data_generation/list_files.toml
|
|
84
84
|
listpick -g list_files.py
|
|
85
85
|
```
|
|
86
86
|
|
|
@@ -100,9 +100,9 @@ The application allows you to:
|
|
|
100
100
|
## Examples
|
|
101
101
|
|
|
102
102
|
|
|
103
|
-
### Identify video duplicates (./examples/video_duplicates.toml):
|
|
103
|
+
### Identify video duplicates (./examples/data_generation//video_duplicates.toml):
|
|
104
104
|
```python
|
|
105
|
-
listpick -g ./examples/video_duplicates.toml
|
|
105
|
+
listpick -g ./examples/data_generation/video_duplicates.toml
|
|
106
106
|
```
|
|
107
107
|
- From the list of commands in the toml file we generate the properties we will use to identify the duplicates.
|
|
108
108
|
|
|
@@ -142,7 +142,7 @@ listpick -i ~/dn.pkl -t pkl
|
|
|
142
142
|
|
|
143
143
|
2. **Generate data based on an toml file with relevant commands to generate the rows.**
|
|
144
144
|
```python
|
|
145
|
-
listpick -g ./examples/video_duplicates.toml
|
|
145
|
+
listpick -g ./examples/data_generation/video_duplicates.toml
|
|
146
146
|
```
|
|
147
147
|
|
|
148
148
|
- See ./examples/
|
|
@@ -9,15 +9,6 @@ setup.py
|
|
|
9
9
|
assets/aria2tui_screenshot.png
|
|
10
10
|
assets/file_compare.png
|
|
11
11
|
assets/lpfman.png
|
|
12
|
-
examples/footer_string_example.py
|
|
13
|
-
examples/input.toml
|
|
14
|
-
examples/input.txt
|
|
15
|
-
examples/list_files.toml
|
|
16
|
-
examples/list_files_empty.toml
|
|
17
|
-
examples/picker_example.py
|
|
18
|
-
examples/setup.cfg
|
|
19
|
-
examples/template.py
|
|
20
|
-
examples/video_duplicates.toml
|
|
21
12
|
src/listpick/__init__.py
|
|
22
13
|
src/listpick/__main__.py
|
|
23
14
|
src/listpick/listpick_app.py
|
|
@@ -30,7 +21,6 @@ src/listpick.egg-info/top_level.txt
|
|
|
30
21
|
src/listpick/ui/__init__.py
|
|
31
22
|
src/listpick/ui/build_help.py
|
|
32
23
|
src/listpick/ui/footer.py
|
|
33
|
-
src/listpick/ui/footer_1.py
|
|
34
24
|
src/listpick/ui/help_screen.py
|
|
35
25
|
src/listpick/ui/input_field.py
|
|
36
26
|
src/listpick/ui/keys.py
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
from listpick.listpick_app import Picker, close_curses, start_curses
|
|
2
|
-
from datetime import datetime
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
l = [["Time"], ["in"], ["footer"], ["string"]]
|
|
6
|
-
|
|
7
|
-
stdscr = start_curses()
|
|
8
|
-
x = Picker(
|
|
9
|
-
stdscr = stdscr,
|
|
10
|
-
items = l,
|
|
11
|
-
title="Footer string example",
|
|
12
|
-
footer_string_auto_refresh=True,
|
|
13
|
-
footer_timer=0.1,
|
|
14
|
-
footer_string_refresh_function=lambda:str(datetime.now()).split('.')[0],
|
|
15
|
-
)
|
|
16
|
-
selected_indices, opts, picker_data = x.run()
|
|
17
|
-
|
|
18
|
-
close_curses(stdscr)
|
|
19
|
-
|
|
20
|
-
print(f"Selected: {selected_indices}")
|
|
21
|
-
print(f"Opts: {opts}")
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
[environment]
|
|
2
|
-
cwd="~/Videos"
|
|
3
|
-
|
|
4
|
-
[data]
|
|
5
|
-
files_command = "eza -1 --no-quotes | grep mp4"
|
|
6
|
-
|
|
7
|
-
commands = [
|
|
8
|
-
"""eza -1 --no-quotes | grep mp4""",
|
|
9
|
-
"""mediainfo {} | grep -i format | head -n 1 | awk -F ':' '{{print $2}}'""",
|
|
10
|
-
"""mediainfo {} | grep -i height | head -n 1 | awk -F ':' '{{print $2}}'""",
|
|
11
|
-
"""mediainfo {} | grep -i width | head -n 1 | awk -F ':' '{{print $2}}'""",
|
|
12
|
-
"""mediainfo {} | grep -i duration | head -n 1 | awk -F ':' '{{print $2}}'""",
|
|
13
|
-
"""mediainfo {} | grep -i 'bit rate' | head -n 1 | awk -F ':' '{{print $2}}'""",
|
|
14
|
-
"""mediainfo {} | grep -i 'frame rate' | head -n 1 | awk -F ':' '{{print $2}}'""",
|
|
15
|
-
]
|
|
16
|
-
|
|
17
|
-
header = [
|
|
18
|
-
"file",
|
|
19
|
-
"format",
|
|
20
|
-
"height",
|
|
21
|
-
"width",
|
|
22
|
-
"duration",
|
|
23
|
-
"bitrate",
|
|
24
|
-
"framerate",
|
|
25
|
-
]
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
--- Environment
|
|
2
|
-
cwd=~/Videos
|
|
3
|
-
|
|
4
|
-
--- Commands
|
|
5
|
-
eza -1 --no-quotes | grep mp4
|
|
6
|
-
mediainfo {} | grep -i format | head -n 1 | awk -F ':' '{{print $2}}'
|
|
7
|
-
mediainfo {} | grep -i height | head -n 1 | awk -F ':' '{{print $2}}'
|
|
8
|
-
mediainfo {} | grep -i width | head -n 1 | awk -F ':' '{{print $2}}'
|
|
9
|
-
mediainfo {} | grep -i duration | head -n 1 | awk -F ':' '{{print $2}}'
|
|
10
|
-
mediainfo {} | grep -i "bit rate" | head -n 1 | awk -F ':' '{{print $2}}'
|
|
11
|
-
mediainfo {} | grep -i "frame rate" | head -n 1 | awk -F ':' '{{print $2}}'
|
|
12
|
-
|
|
13
|
-
--- Header
|
|
14
|
-
file
|
|
15
|
-
formatkajsdflkjasfdjkasfdj
|
|
16
|
-
height
|
|
17
|
-
width
|
|
18
|
-
duration
|
|
19
|
-
bitrate
|
|
20
|
-
framerate0123456789
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import sys, os
|
|
2
|
-
from listpick.listpick_app import Picker, close_curses, start_curses
|
|
3
|
-
|
|
4
|
-
l = [["The", "many"], ["words", "of"]]
|
|
5
|
-
l += [["the", "Athenians"], ["I", "do"]]
|
|
6
|
-
l += [["not", "understand."], ["They", "said"]]
|
|
7
|
-
l += [["a great", "deal"], ["in praise", "of themselves"]]
|
|
8
|
-
l += [["but", "nowhere"], ["denied", "that they"]]
|
|
9
|
-
l += [["are", "injuring"], ["our", "allies"]]
|
|
10
|
-
|
|
11
|
-
header=["Pericles", "Is Dead"]
|
|
12
|
-
|
|
13
|
-
highlights = [
|
|
14
|
-
{
|
|
15
|
-
"match": "praise",
|
|
16
|
-
"field": 0,
|
|
17
|
-
"color": 8,
|
|
18
|
-
},
|
|
19
|
-
{
|
|
20
|
-
"match": "theni..",
|
|
21
|
-
"field": 1,
|
|
22
|
-
"color": 9,
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
"match": ".*",
|
|
26
|
-
"row": 5,
|
|
27
|
-
"field": 1,
|
|
28
|
-
"color": 11,
|
|
29
|
-
},
|
|
30
|
-
]
|
|
31
|
-
|
|
32
|
-
stdscr = start_curses()
|
|
33
|
-
x = Picker(
|
|
34
|
-
stdscr = stdscr,
|
|
35
|
-
items = l,
|
|
36
|
-
title="Test Picker",
|
|
37
|
-
header=header,
|
|
38
|
-
highlights=highlights,
|
|
39
|
-
)
|
|
40
|
-
selected_indices, opts, picker_data = x.run()
|
|
41
|
-
|
|
42
|
-
close_curses(stdscr)
|
|
43
|
-
|
|
44
|
-
print(f"Selected: {selected_indices}")
|
|
45
|
-
print(f"Opts: {opts}")
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
[metadata]
|
|
2
|
-
name = listpick
|
|
3
|
-
version = 0.1.4
|
|
4
|
-
author = Grim
|
|
5
|
-
author_email = grimandgreedy@protonmail.com
|
|
6
|
-
description = Listpick is a powerful TUI data tool for creating TUI apps or viewing/comparing tabulated data.
|
|
7
|
-
; long_description = file: README.md, LICENSE.txt
|
|
8
|
-
long_description = file: README.md
|
|
9
|
-
long_description_content_type = text/markdown
|
|
10
|
-
url = https://github.com/grimandgreedy/listpick
|
|
11
|
-
project_urls =
|
|
12
|
-
Bug Tracker = https://github.com/grimandgreedy/listpick/issues
|
|
13
|
-
repository = https://github.com/grimandgreedy/listpick
|
|
14
|
-
classifiers =
|
|
15
|
-
Programming Language :: Python :: 3
|
|
16
|
-
; License :: OSI Approved :: MIT License
|
|
17
|
-
Operating System :: Unix
|
|
18
|
-
|
|
19
|
-
[options]
|
|
20
|
-
package_dir =
|
|
21
|
-
= src
|
|
22
|
-
packages = find:
|
|
23
|
-
python_requires = >=3.1
|
|
24
|
-
|
|
25
|
-
[options.packages.find]
|
|
26
|
-
where = src
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
from listpick.listpick_app import Picker, close_curses, start_curses
|
|
2
|
-
|
|
3
|
-
l = [["1", "2"], ["3", "4"]]
|
|
4
|
-
header=["Pericles", "Is Dead"]
|
|
5
|
-
|
|
6
|
-
stdscr = start_curses()
|
|
7
|
-
x = Picker(
|
|
8
|
-
stdscr = stdscr,
|
|
9
|
-
items = l,
|
|
10
|
-
title="Test Picker",
|
|
11
|
-
header=header,
|
|
12
|
-
highlights=highlights,
|
|
13
|
-
)
|
|
14
|
-
selected_indices, opts, picker_data = x.run()
|
|
15
|
-
|
|
16
|
-
close_curses(stdscr)
|
|
17
|
-
|
|
18
|
-
print(f"Selected: {selected_indices}")
|
|
19
|
-
print(f"Opts: {opts}")
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
[environment]
|
|
2
|
-
cwd="~/Downloads/new/"
|
|
3
|
-
|
|
4
|
-
[data]
|
|
5
|
-
files_command = "eza -1 --no-quotes | grep mp4"
|
|
6
|
-
|
|
7
|
-
commands = [
|
|
8
|
-
"""eza -1 --no-quotes | grep mp4""",
|
|
9
|
-
# """sha1sum {} | awk '{{print $1}}'""",
|
|
10
|
-
"""ffprobe -show_entries format=duration -v quiet -of csv="p=0" -i {}""",
|
|
11
|
-
"""ffprobe -v error -select_streams v:0 -show_entries stream=width -of csv=s=x:p=0 {}""",
|
|
12
|
-
"""ffprobe -v error -select_streams v:0 -show_entries stream=height -of csv=s=x:p=0 {}"""
|
|
13
|
-
"""du -hs {} | awk '{{ print $1 }}'"""
|
|
14
|
-
]
|
|
15
|
-
|
|
16
|
-
header = [
|
|
17
|
-
"file",
|
|
18
|
-
# "sha1",
|
|
19
|
-
"duration",
|
|
20
|
-
"width",
|
|
21
|
-
"height",
|
|
22
|
-
"size",
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
run = [
|
|
26
|
-
"mpv",
|
|
27
|
-
]
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
footer.py
|
|
3
|
-
Lines to be displayed on the help screen.
|
|
4
|
-
|
|
5
|
-
Author: GrimAndGreedy
|
|
6
|
-
License: MIT
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import curses
|
|
10
|
-
import logging
|
|
11
|
-
|
|
12
|
-
logger = logging.getLogger('picker_log')
|
|
13
|
-
|
|
14
|
-
class Footer:
|
|
15
|
-
def __init__(self, stdscr, colours_start, get_state_function):
|
|
16
|
-
"""
|
|
17
|
-
stdscr: curses screen object
|
|
18
|
-
colours_start: base colour pair index
|
|
19
|
-
get_state_callback: function that returns a dict with all required data for rendering
|
|
20
|
-
"""
|
|
21
|
-
self.stdscr = stdscr
|
|
22
|
-
self.colours_start = colours_start
|
|
23
|
-
self.get_state = get_state_function
|
|
24
|
-
self.height = 0
|
|
25
|
-
|
|
26
|
-
def draw(self, h, w):
|
|
27
|
-
"""
|
|
28
|
-
Draw the footer. Must be implemented by subclasses.
|
|
29
|
-
"""
|
|
30
|
-
raise NotImplementedError
|
|
31
|
-
|
|
32
|
-
class StandardFooter(Footer):
|
|
33
|
-
def __init__(self, stdscr, colours_start, get_state_function):
|
|
34
|
-
"""
|
|
35
|
-
stdscr: curses screen object
|
|
36
|
-
colours_start: base colour pair index
|
|
37
|
-
get_state_callback: function that returns a dict with all required data for rendering
|
|
38
|
-
"""
|
|
39
|
-
self.stdscr = stdscr
|
|
40
|
-
self.colours_start = colours_start
|
|
41
|
-
self.get_state = get_state_function
|
|
42
|
-
self.height = 3
|
|
43
|
-
def draw(self, h, w):
|
|
44
|
-
state = self.get_state()
|
|
45
|
-
|
|
46
|
-
# Fill background
|
|
47
|
-
for i in range(3):
|
|
48
|
-
self.stdscr.addstr(h-3+i, 0, ' '*(w-1), curses.color_pair(self.colours_start+20))
|
|
49
|
-
|
|
50
|
-
if state["filter_query"]:
|
|
51
|
-
self.stdscr.addstr(h - 2, 2, f" Filter: {state['filter_query']} "[:w-40], curses.color_pair(self.colours_start+20) | curses.A_BOLD)
|
|
52
|
-
if state["search_query"]:
|
|
53
|
-
self.stdscr.addstr(h - 3, 2, f" Search: {state['search_query']} [{state['search_index']}/{state['search_count']}] "[:w-3], curses.color_pair(self.colours_start+20) | curses.A_BOLD)
|
|
54
|
-
if state["user_opts"]:
|
|
55
|
-
self.stdscr.addstr(h - 1, 2, f" Opts: {state['user_opts']} "[:w-3], curses.color_pair(self.colours_start+20) | curses.A_BOLD)
|
|
56
|
-
|
|
57
|
-
# Sort info
|
|
58
|
-
sort_column_info = f"{state['sort_column'] if state['sort_column'] is not None else 'None'}"
|
|
59
|
-
sort_method_info = f"{state['SORT_METHODS'][state['columns_sort_method'][state['sort_column']]]}" if state['sort_column'] is not None else "NA"
|
|
60
|
-
sort_order_info = "Desc." if state["sort_reverse"] else "Asc."
|
|
61
|
-
sort_order_info = "▼" if state["sort_reverse"][state['sort_column']] else "▲"
|
|
62
|
-
sort_disp_str = f" Sort: ({sort_column_info}, {sort_method_info}, {sort_order_info}) "
|
|
63
|
-
self.stdscr.addstr(h - 2, w-35, f"{sort_disp_str:>34}", curses.color_pair(self.colours_start+20))
|
|
64
|
-
|
|
65
|
-
if state["footer_string"]:
|
|
66
|
-
# footer_string_width = min(w-1, max(len(state["footer_string"]), 50))
|
|
67
|
-
# disp_string = f"{state['footer_string'][:footer_string_width]:>{footer_string_width-1}} "
|
|
68
|
-
# self.stdscr.addstr(h - 1, w-footer_string_width-1, " "*footer_string_width, curses.color_pair(self.colours_start+24))
|
|
69
|
-
# self.stdscr.addstr(h - 1, w-footer_string_width-1, f"{disp_string}", curses.color_pair(self.colours_start+24))
|
|
70
|
-
|
|
71
|
-
# disp_string = f"{footer_string:>{footer_string_width-1}} "
|
|
72
|
-
# self.stdscr.addstr(h - 1, w-footer_string_width-1, " "*footer_string_width, curses.color_pair(self.colours_start+24))
|
|
73
|
-
# self.stdscr.addstr(h - 1, w-footer_string_width-1, f"{disp_string}", curses.color_pair(self.colours_start+24))
|
|
74
|
-
|
|
75
|
-
footer_string_width = min(w-1, len(state["footer_string"])+2)
|
|
76
|
-
|
|
77
|
-
disp_string = f"{state["footer_string"][:footer_string_width]}"
|
|
78
|
-
disp_string = f" {disp_string:>{footer_string_width-2}} "
|
|
79
|
-
self.stdscr.addstr(h - 1, w-footer_string_width-1, " "*footer_string_width, curses.color_pair(self.colours_start+24))
|
|
80
|
-
self.stdscr.addstr(h - 1, w-footer_string_width-1, f"{disp_string}", curses.color_pair(self.colours_start+24))
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
else:
|
|
84
|
-
select_mode = "Cursor"
|
|
85
|
-
if state["is_selecting"]: select_mode = "Visual Selection"
|
|
86
|
-
elif state["is_deselecting"]: select_mode = "Visual deselection"
|
|
87
|
-
self.stdscr.addstr(h - 1, w-35, f"{select_mode:>33} ", curses.color_pair(self.colours_start+20))
|
|
88
|
-
|
|
89
|
-
# Cursor & selection info
|
|
90
|
-
selected_count = sum(state["selections"].values())
|
|
91
|
-
if state["paginate"]:
|
|
92
|
-
cursor_disp_str = f" {state['cursor_pos']+1}/{len(state['indexed_items'])} Page {state['cursor_pos']//state['items_per_page']}/{len(state['indexed_items'])} Selected {selected_count}"
|
|
93
|
-
else:
|
|
94
|
-
cursor_disp_str = f" {state['cursor_pos']+1}/{len(state['indexed_items'])} | Selected {selected_count}"
|
|
95
|
-
self.stdscr.addstr(h - 3, w-35, f"{cursor_disp_str:>33} ", curses.color_pair(self.colours_start+20))
|
|
96
|
-
|
|
97
|
-
self.stdscr.refresh()
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
class CompactFooter(Footer):
|
|
102
|
-
def __init__(self, stdscr, colours_start, get_state_function):
|
|
103
|
-
"""
|
|
104
|
-
stdscr: curses screen object
|
|
105
|
-
colours_start: base colour pair index
|
|
106
|
-
get_state_callback: function that returns a dict with all required data for rendering
|
|
107
|
-
"""
|
|
108
|
-
self.stdscr = stdscr
|
|
109
|
-
self.colours_start = colours_start
|
|
110
|
-
self.get_state = get_state_function
|
|
111
|
-
self.height = 1
|
|
112
|
-
|
|
113
|
-
def draw(self, h, w):
|
|
114
|
-
state = self.get_state()
|
|
115
|
-
|
|
116
|
-
# Fill background
|
|
117
|
-
if state["search_query"]: self.height = 3
|
|
118
|
-
elif state["filter_query"]: self.height = 2
|
|
119
|
-
elif state["user_opts"]: self.height = 1
|
|
120
|
-
elif state["footer_string"]: self.height = 2
|
|
121
|
-
else: self.height = 1
|
|
122
|
-
for i in range(self.height):
|
|
123
|
-
self.stdscr.addstr(h-(i+1), 0, ' '*(w-1), curses.color_pair(self.colours_start+20))
|
|
124
|
-
|
|
125
|
-
if state["user_opts"]:
|
|
126
|
-
self.stdscr.addstr(h - 1, 2, f" Opts: {state['user_opts']} "[:w-3], curses.color_pair(self.colours_start+20) | curses.A_BOLD)
|
|
127
|
-
if state["filter_query"]:
|
|
128
|
-
self.stdscr.addstr(h - 2, 2, f" Filter: {state['filter_query']} "[:w-40], curses.color_pair(self.colours_start+20) | curses.A_BOLD)
|
|
129
|
-
if state["search_query"]:
|
|
130
|
-
self.stdscr.addstr(h - 3, 2, f" Search: {state['search_query']} [{state['search_index']}/{state['search_count']}] "[:w-3], curses.color_pair(self.colours_start+20) | curses.A_BOLD)
|
|
131
|
-
|
|
132
|
-
right_width = 40
|
|
133
|
-
# Sort info
|
|
134
|
-
sort_column_info = f"{state['sort_column'] if state['sort_column'] is not None else 'None'}"
|
|
135
|
-
sort_method_info = f"{state['SORT_METHODS'][state['columns_sort_method'][state['sort_column']]]}" if state['sort_column'] is not None else "NA"
|
|
136
|
-
sort_order_info = "Desc." if state["sort_reverse"][state['sort_column']] else "Asc."
|
|
137
|
-
sort_order_info = "▼" if state["sort_reverse"][state['sort_column']] else "▲"
|
|
138
|
-
sort_disp_str = f" ({sort_column_info}, {sort_method_info}, {sort_order_info}) "
|
|
139
|
-
# self.stdscr.addstr(h - 2, w-right_width, f"{sort_disp_str:>{right_width-1}}", curses.color_pair(self.colours_start+20))
|
|
140
|
-
|
|
141
|
-
if state["footer_string"]:
|
|
142
|
-
footer_string_width = min(w-1, len(state["footer_string"])+2)
|
|
143
|
-
|
|
144
|
-
disp_string = f"{state["footer_string"][:footer_string_width]}"
|
|
145
|
-
disp_string = f" {disp_string:>{footer_string_width-2}} "
|
|
146
|
-
self.stdscr.addstr(h - 1, w-footer_string_width-1, " "*footer_string_width, curses.color_pair(self.colours_start+24))
|
|
147
|
-
self.stdscr.addstr(h - 1, w-footer_string_width-1, f"{disp_string}", curses.color_pair(self.colours_start+24))
|
|
148
|
-
selected_count = sum(state["selections"].values())
|
|
149
|
-
if state["paginate"]:
|
|
150
|
-
cursor_disp_str = f" {state['cursor_pos']+1}/{len(state['indexed_items'])} Page {state['cursor_pos']//state['items_per_page']}/{len(state['indexed_items'])} Selected {selected_count}"
|
|
151
|
-
else:
|
|
152
|
-
cursor_disp_str = f"{sort_disp_str} [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])}"
|
|
153
|
-
self.stdscr.addstr(h-2, w-right_width, f"{cursor_disp_str:>{right_width-2}}"[:right_width-1], curses.color_pair(self.colours_start+20))
|
|
154
|
-
else:
|
|
155
|
-
# Cursor & selection info
|
|
156
|
-
selected_count = sum(state["selections"].values())
|
|
157
|
-
if state["paginate"]:
|
|
158
|
-
cursor_disp_str = f" {state['cursor_pos']+1}/{len(state['indexed_items'])} Page {state['cursor_pos']//state['items_per_page']}/{len(state['indexed_items'])} Selected {selected_count}"
|
|
159
|
-
else:
|
|
160
|
-
cursor_disp_str = f"{sort_disp_str} [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])}"
|
|
161
|
-
self.stdscr.addstr(h - 1, w-right_width, f"{cursor_disp_str:>{right_width-2}}"[:right_width-1], curses.color_pair(self.colours_start+20))
|
|
162
|
-
|
|
163
|
-
self.stdscr.refresh()
|
|
164
|
-
|
|
165
|
-
class NoFooter(Footer):
|
|
166
|
-
def __init__(self, stdscr, colours_start, get_state_function):
|
|
167
|
-
"""
|
|
168
|
-
stdscr: curses screen object
|
|
169
|
-
colours_start: base colour pair index
|
|
170
|
-
get_state_callback: function that returns a dict with all required data for rendering
|
|
171
|
-
"""
|
|
172
|
-
self.stdscr = stdscr
|
|
173
|
-
self.colours_start = colours_start
|
|
174
|
-
self.get_state = get_state_function
|
|
175
|
-
self.height = 0
|
|
176
|
-
def draw(self, h, w):
|
|
177
|
-
state = self.get_state()
|
|
178
|
-
|
|
179
|
-
if state["search_query"]: self.height = 3
|
|
180
|
-
elif state["filter_query"]: self.height = 2
|
|
181
|
-
elif state["user_opts"]: self.height = 1
|
|
182
|
-
elif state["footer_string"]: self.height = 1
|
|
183
|
-
else: self.height = 0
|
|
184
|
-
|
|
185
|
-
for i in range(self.height):
|
|
186
|
-
self.stdscr.addstr(h-(i+1), 0, ' '*(w-1), curses.color_pair(self.colours_start+20))
|
|
187
|
-
|
|
188
|
-
if state["user_opts"]:
|
|
189
|
-
self.stdscr.addstr(h - 1, 2, f" Opts: {state['user_opts']} "[:w-3], curses.color_pair(self.colours_start+20) | curses.A_BOLD)
|
|
190
|
-
if state["filter_query"]:
|
|
191
|
-
self.stdscr.addstr(h - 2, 2, f" Filter: {state['filter_query']} "[:w-40], curses.color_pair(self.colours_start+20) | curses.A_BOLD)
|
|
192
|
-
if state["search_query"]:
|
|
193
|
-
self.stdscr.addstr(h - 3, 2, f" Search: {state['search_query']} [{state['search_index']}/{state['search_count']}] "[:w-3], curses.color_pair(self.colours_start+20) | curses.A_BOLD)
|
|
194
|
-
self.height = 3
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
if state["footer_string"]:
|
|
198
|
-
footer_string_width = min(w-1, len(state["footer_string"])+2)
|
|
199
|
-
disp_string = f"{state["footer_string"][:footer_string_width]}"
|
|
200
|
-
disp_string = f" {disp_string:>{footer_string_width-2}} "
|
|
201
|
-
self.stdscr.addstr(h - 1, w-footer_string_width-1, " "*footer_string_width, curses.color_pair(self.colours_start+24))
|
|
202
|
-
self.stdscr.addstr(h - 1, w-footer_string_width-1, f"{disp_string}", curses.color_pair(self.colours_start+24))
|
|
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
|