listpick 0.1.13.62__tar.gz → 0.1.14.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of listpick might be problematic. Click here for more details.
- {listpick-0.1.13.62 → listpick-0.1.14.0}/CHANGELOG.md +10 -0
- {listpick-0.1.13.62/src/listpick.egg-info → listpick-0.1.14.0}/PKG-INFO +2 -1
- {listpick-0.1.13.62 → listpick-0.1.14.0}/TODO.md +17 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/requirements.txt +1 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/setup.py +2 -1
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/listpick_app.py +280 -61
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/ui/footer.py +98 -16
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/table_to_list_of_lists.py +51 -36
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/utils.py +5 -4
- {listpick-0.1.13.62 → listpick-0.1.14.0/src/listpick.egg-info}/PKG-INFO +2 -1
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick.egg-info/requires.txt +1 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/.gitignore +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/LICENSE.txt +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/README.md +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/assets/aria2tui_screenshot.png +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/assets/file_compare.png +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/assets/lpfman.png +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/listpick.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/setup.cfg +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/__init__.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/__main__.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/ui/__init__.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/ui/build_help.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/ui/help_screen.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/ui/input_field.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/ui/keys.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/ui/pane_stuff.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/ui/picker_colours.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/__init__.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/clipboard_operations.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/config.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/dump.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/filtering.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/generate_data.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/options_selectors.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/paste_operations.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/picker_log.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/search_and_filter_utils.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/searching.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick/utils/sorting.py +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick.egg-info/SOURCES.txt +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick.egg-info/dependency_links.txt +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick.egg-info/entry_points.txt +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/src/listpick.egg-info/top_level.txt +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/tests/kitty_control.sh +0 -0
- {listpick-0.1.13.62 → listpick-0.1.14.0}/tests/sorting_dates.csv +0 -0
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
- Bug fixes:
|
|
11
11
|
- When data was centred vertically it would take an extra draw_screen loop to determine the proper column widths when the column sizes changed. This has been fixed.
|
|
12
12
|
- Refreshing sorted data would resort it on the selected column rather than the sort column. Fixed.
|
|
13
|
+
- Fixed error when padding uneven lists when a list of strings is passed instead of a list of lists.
|
|
14
|
+
- Fixed crash when showing certain notifications. The notification message was not being split into lines properly.
|
|
15
|
+
- Fixed wrong page number in the footer when paginate=True.
|
|
13
16
|
- Added some extra options to the settings:
|
|
14
17
|
- Toggle header
|
|
15
18
|
- Toggle row header
|
|
@@ -31,6 +34,13 @@
|
|
|
31
34
|
- Much faster with very large data sets as we need to determine selected_cells_by_row every time we run self.draw_screen()
|
|
32
35
|
- We can now pipe data from cells in multiple columns to a command.
|
|
33
36
|
- e.g., pipe two cols to gnuplot
|
|
37
|
+
- Features added:
|
|
38
|
+
- Listpick now supports multiple open files.
|
|
39
|
+
- 'file_next' setting
|
|
40
|
+
- Listpick now supports files with multiple sheets.
|
|
41
|
+
- 'sheet_next' setting
|
|
42
|
+
- Fixed error when opening xlsx files.
|
|
43
|
+
- Can now open multiple files from the command line: listpick -i file1.csv, file2.csv
|
|
34
44
|
|
|
35
45
|
## [0.1.13] 2025-07-28
|
|
36
46
|
- Cell-based picker is now supported.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: listpick
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14.0
|
|
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
|
|
@@ -26,6 +26,7 @@ Requires-Dist: pandas; extra == "full"
|
|
|
26
26
|
Requires-Dist: pyperclip; extra == "full"
|
|
27
27
|
Requires-Dist: toml; extra == "full"
|
|
28
28
|
Requires-Dist: traitlets; extra == "full"
|
|
29
|
+
Requires-Dist: odfpy; extra == "full"
|
|
29
30
|
Dynamic: author
|
|
30
31
|
Dynamic: author-email
|
|
31
32
|
Dynamic: classifier
|
|
@@ -5,6 +5,21 @@ ASAP
|
|
|
5
5
|
> - [ ] Unify in-app load and command-line input file
|
|
6
6
|
> - [ ] Implement default_option_selector and pass the picker options (!!!)
|
|
7
7
|
> - [ ] Make sure that all class initialisation variables are returned in the get_function_variables function.
|
|
8
|
+
> - [ ] Go through each of the class variables and see which should be held as common in set_function_data
|
|
9
|
+
> - [ ] Make input field look better:
|
|
10
|
+
> - [ ] During input
|
|
11
|
+
> - [ ] While being displayed.
|
|
12
|
+
> - [ ] Handle multiple files
|
|
13
|
+
> - [x] Display 'open' files in footer
|
|
14
|
+
> - [x] Open multiple files with file_picker
|
|
15
|
+
> - [x] Switch between 'open' files
|
|
16
|
+
> - [ ] Handle unsaved files.
|
|
17
|
+
> - [x] Remember state when switching between files.
|
|
18
|
+
> - [x] Selections
|
|
19
|
+
> - [ ] Add support in the alternate footers
|
|
20
|
+
> - [x] Handle files with multiple sheets.
|
|
21
|
+
> - [ ] Add support in the alternate footers
|
|
22
|
+
> - [ ] Support opening multiple files from the command line.
|
|
8
23
|
|
|
9
24
|
|
|
10
25
|
|
|
@@ -259,6 +274,8 @@ ASAP
|
|
|
259
274
|
> - [ ] Add a broader editor_picker option which enables/disables add/remove columns in the settings.
|
|
260
275
|
> - [ ] If no -t type argument is given then guess filetype.
|
|
261
276
|
> - [ ] Add row selection using 'V'--select all cells in a row.
|
|
277
|
+
> - [ ] Add option for those who don't use a nerdfont.
|
|
278
|
+
> - [ ] refreshing symbol, pin cursor symbol,
|
|
262
279
|
|
|
263
280
|
> [!Bug] Bugs
|
|
264
281
|
> - [ ] fix resizing when input field active
|
|
@@ -16,7 +16,7 @@ with open("README.md", "r", encoding = "utf-8") as fh:
|
|
|
16
16
|
|
|
17
17
|
setuptools.setup(
|
|
18
18
|
name = "listpick",
|
|
19
|
-
version = "0.1.
|
|
19
|
+
version = "0.1.14.0",
|
|
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.",
|
|
@@ -59,6 +59,7 @@ setuptools.setup(
|
|
|
59
59
|
"pyperclip",
|
|
60
60
|
"toml",
|
|
61
61
|
"traitlets",
|
|
62
|
+
"odfpy",
|
|
62
63
|
]
|
|
63
64
|
},
|
|
64
65
|
|
|
@@ -106,6 +106,7 @@ class Picker:
|
|
|
106
106
|
|
|
107
107
|
selections: dict = {},
|
|
108
108
|
cell_selections: dict[tuple[int,int], bool] = {},
|
|
109
|
+
selected_cells_by_row: dict = {},
|
|
109
110
|
highlight_full_row: bool =False,
|
|
110
111
|
cell_cursor: bool = False,
|
|
111
112
|
|
|
@@ -173,6 +174,19 @@ class Picker:
|
|
|
173
174
|
debug: bool = False,
|
|
174
175
|
debug_level: int = 1,
|
|
175
176
|
|
|
177
|
+
command_stack: list = [],
|
|
178
|
+
|
|
179
|
+
loaded_file: str = "Untitled",
|
|
180
|
+
loaded_files: list[str] = ["Untitled"],
|
|
181
|
+
loaded_file_index: int = 0,
|
|
182
|
+
loaded_file_states: list[dict] = [{}],
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
sheets = ["Untitled"],
|
|
186
|
+
sheet_name = "Untitled",
|
|
187
|
+
sheet_index = 0,
|
|
188
|
+
sheet_states = [{}],
|
|
189
|
+
|
|
176
190
|
):
|
|
177
191
|
self.stdscr = stdscr
|
|
178
192
|
self.items = items
|
|
@@ -225,6 +239,7 @@ class Picker:
|
|
|
225
239
|
|
|
226
240
|
self.selections = selections
|
|
227
241
|
self.cell_selections = cell_selections
|
|
242
|
+
self.selected_cells_by_row = selected_cells_by_row
|
|
228
243
|
self.highlight_full_row = highlight_full_row
|
|
229
244
|
self.cell_cursor = cell_cursor
|
|
230
245
|
|
|
@@ -283,7 +298,7 @@ class Picker:
|
|
|
283
298
|
self.registers = {}
|
|
284
299
|
|
|
285
300
|
self.SORT_METHODS = SORT_METHODS
|
|
286
|
-
self.command_stack =
|
|
301
|
+
self.command_stack = command_stack
|
|
287
302
|
self.leftmost_column = leftmost_column
|
|
288
303
|
self.leftmost_char = leftmost_char
|
|
289
304
|
|
|
@@ -297,7 +312,6 @@ class Picker:
|
|
|
297
312
|
self.cursor_pos_prev = 0
|
|
298
313
|
self.ids = []
|
|
299
314
|
self.ids_tuples = []
|
|
300
|
-
self.selected_cells_by_row = {}
|
|
301
315
|
|
|
302
316
|
# History variables
|
|
303
317
|
self.history_filter_and_search = history_filter_and_search
|
|
@@ -312,6 +326,17 @@ class Picker:
|
|
|
312
326
|
self.debug = debug
|
|
313
327
|
self.debug_level = debug_level
|
|
314
328
|
|
|
329
|
+
# Multiple file support
|
|
330
|
+
self.loaded_files = loaded_files
|
|
331
|
+
self.loaded_file = loaded_file
|
|
332
|
+
self.loaded_file_index = loaded_file_index
|
|
333
|
+
self.loaded_file_states = loaded_file_states
|
|
334
|
+
|
|
335
|
+
# Multiple sheet support
|
|
336
|
+
self.sheet_index = sheet_index
|
|
337
|
+
self.sheet_name = sheet_name
|
|
338
|
+
self.sheet_states = sheet_states
|
|
339
|
+
self.sheets = sheets
|
|
315
340
|
|
|
316
341
|
self.initialise_picker_state(reset_colours=self.reset_colours)
|
|
317
342
|
|
|
@@ -320,6 +345,8 @@ class Picker:
|
|
|
320
345
|
self.footer = self.footer_options[self.footer_style]
|
|
321
346
|
|
|
322
347
|
|
|
348
|
+
|
|
349
|
+
|
|
323
350
|
def calculate_section_sizes(self):
|
|
324
351
|
"""
|
|
325
352
|
Calculte the following for the Picker:
|
|
@@ -495,8 +522,12 @@ class Picker:
|
|
|
495
522
|
|
|
496
523
|
if len(self.items) and len(self.cell_selections) != len(self.items)*len(self.items[0]):
|
|
497
524
|
self.cell_selections = {(i, j) : False if (i, j) not in self.cell_selections else self.cell_selections[(i, j)] for i in range(len(self.items)) for j in range(len(self.items[0]))}
|
|
525
|
+
self.selected_cells_by_row = get_selected_cells_by_row(self.cell_selections)
|
|
498
526
|
elif len(self.items) == 0:
|
|
499
527
|
self.cell_selections = {}
|
|
528
|
+
self.selected_cells_by_row = {}
|
|
529
|
+
|
|
530
|
+
|
|
500
531
|
|
|
501
532
|
if len(self.require_option) < len(self.items):
|
|
502
533
|
self.require_option += [self.require_option_default for i in range(len(self.items)-len(self.require_option))]
|
|
@@ -556,6 +587,20 @@ class Picker:
|
|
|
556
587
|
assert new_pos < len(self.items)
|
|
557
588
|
self.cursor_pos = new_pos
|
|
558
589
|
|
|
590
|
+
# Sheets and files
|
|
591
|
+
if len(self.sheet_states) < len(self.sheets):
|
|
592
|
+
self.sheet_states += [{} for _ in range(len(self.sheets) - len(self.sheet_states))]
|
|
593
|
+
if len(self.sheets):
|
|
594
|
+
if self.sheet_index >= len(self.sheets):
|
|
595
|
+
self.sheet_index = 0
|
|
596
|
+
self.sheet_name = self.sheets[self.sheet_index]
|
|
597
|
+
|
|
598
|
+
if len(self.loaded_file_states) < len(self.loaded_files):
|
|
599
|
+
self.loaded_file_states += [{} for _ in range(len(self.loaded_files) - len(self.loaded_file_states))]
|
|
600
|
+
if len(self.loaded_files):
|
|
601
|
+
if self.loaded_file_index >= len(self.loaded_files):
|
|
602
|
+
self.loaded_file_index = 0
|
|
603
|
+
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
559
604
|
|
|
560
605
|
# if tracking and len(self.items) > 1:
|
|
561
606
|
# Ensure that selected indices are tracked upon data refresh
|
|
@@ -591,6 +636,7 @@ class Picker:
|
|
|
591
636
|
self.cursor_pos = [i[0] for i in self.indexed_items].index(cursor_pos_x)
|
|
592
637
|
else:
|
|
593
638
|
self.cursor_pos = 0
|
|
639
|
+
|
|
594
640
|
|
|
595
641
|
|
|
596
642
|
|
|
@@ -1045,6 +1091,7 @@ class Picker:
|
|
|
1045
1091
|
function_data = {
|
|
1046
1092
|
"selections": self.selections,
|
|
1047
1093
|
"cell_selections": self.cell_selections,
|
|
1094
|
+
"selected_cells_by_row": self.selected_cells_by_row,
|
|
1048
1095
|
"items_per_page": self.items_per_page,
|
|
1049
1096
|
"current_row": self.current_row,
|
|
1050
1097
|
"current_page": self.current_page,
|
|
@@ -1136,17 +1183,55 @@ class Picker:
|
|
|
1136
1183
|
"debug_level": self.debug_level,
|
|
1137
1184
|
"reset_colours": self.reset_colours,
|
|
1138
1185
|
"unicode_char_width": self.unicode_char_width,
|
|
1186
|
+
"command_stack": self.command_stack,
|
|
1187
|
+
"loaded_file": self.loaded_file,
|
|
1188
|
+
"loaded_files": self.loaded_files,
|
|
1189
|
+
"loaded_file_index": self.loaded_file_index,
|
|
1190
|
+
"loaded_file_states": self.loaded_file_states,
|
|
1191
|
+
"sheet_index": self.sheet_index,
|
|
1192
|
+
"sheets": self.sheets,
|
|
1193
|
+
"sheet_name": self.sheet_name,
|
|
1194
|
+
"sheet_states": self.sheet_states,
|
|
1139
1195
|
}
|
|
1140
1196
|
return function_data
|
|
1141
1197
|
|
|
1142
|
-
def set_function_data(self, function_data: dict) -> None:
|
|
1198
|
+
def set_function_data(self, function_data: dict, reset_absent_variables: bool = False, do_not_set: list=[]) -> None:
|
|
1143
1199
|
""" Set variables from state dict containing core variables."""
|
|
1144
1200
|
self.logger.info(f"function: set_function_data()")
|
|
1145
1201
|
variables = self.get_function_data().keys()
|
|
1146
1202
|
|
|
1203
|
+
x = Picker(self.stdscr, reset_colours=False)
|
|
1204
|
+
|
|
1205
|
+
|
|
1206
|
+
common_picker_vars = [
|
|
1207
|
+
"loaded_file_index",
|
|
1208
|
+
"loaded_file_states",
|
|
1209
|
+
"loaded_files",
|
|
1210
|
+
"loaded_file",
|
|
1211
|
+
"command_stack",
|
|
1212
|
+
"colour_theme_number",
|
|
1213
|
+
"reset_colours",
|
|
1214
|
+
"show_footer",
|
|
1215
|
+
"show_header",
|
|
1216
|
+
"history_filter_and_search",
|
|
1217
|
+
"history_settings",
|
|
1218
|
+
"history_opts",
|
|
1219
|
+
"history_edits",
|
|
1220
|
+
"history_pipes",
|
|
1221
|
+
"reset_colours",
|
|
1222
|
+
"cell_cursor",
|
|
1223
|
+
"top_gap",
|
|
1224
|
+
"unicode_char_width",
|
|
1225
|
+
"show_row_header",
|
|
1226
|
+
]
|
|
1227
|
+
|
|
1147
1228
|
for var in variables:
|
|
1148
1229
|
if var in function_data:
|
|
1149
1230
|
setattr(self, var, function_data[var])
|
|
1231
|
+
elif reset_absent_variables and var not in common_picker_vars and var not in do_not_set:
|
|
1232
|
+
# Set value to the default for an empty picker
|
|
1233
|
+
setattr(self, var, getattr(x, var))
|
|
1234
|
+
|
|
1150
1235
|
|
|
1151
1236
|
reset_colours = bool("colour_theme_number" in function_data)
|
|
1152
1237
|
self.initialise_picker_state(reset_colours=reset_colours)
|
|
@@ -1275,6 +1360,7 @@ class Picker:
|
|
|
1275
1360
|
h, w = stdscr.getmaxyx()
|
|
1276
1361
|
|
|
1277
1362
|
submenu_win = curses.newwin(notification_height, notification_width, 3, w - (notification_width+4))
|
|
1363
|
+
# submenu_win = self.stdscr.subwin(notification_height, notification_width, 3, w - (notification_width+4))
|
|
1278
1364
|
notification_data = {
|
|
1279
1365
|
"items": submenu_items,
|
|
1280
1366
|
"title": title,
|
|
@@ -1291,6 +1377,10 @@ class Picker:
|
|
|
1291
1377
|
"cancel_is_back": True,
|
|
1292
1378
|
"reset_colours": False,
|
|
1293
1379
|
|
|
1380
|
+
"loaded_files": [],
|
|
1381
|
+
"loaded_file_states": [],
|
|
1382
|
+
"loaded_file": "",
|
|
1383
|
+
"loaded_file_index": 0,
|
|
1294
1384
|
}
|
|
1295
1385
|
OptionPicker = Picker(submenu_win, **notification_data)
|
|
1296
1386
|
s, o, f = OptionPicker.run()
|
|
@@ -1343,15 +1433,15 @@ class Picker:
|
|
|
1343
1433
|
# highlights = [highlight for highlight in highlights if "type" not in highlight or highlight["type"] != "search" ]
|
|
1344
1434
|
|
|
1345
1435
|
self.highlights_hide = not self.highlights_hide
|
|
1346
|
-
elif setting[0] == "s":
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1436
|
+
# elif setting[0] == "s":
|
|
1437
|
+
# if 0 <= int(setting[1:]) < len(self.items[0]):
|
|
1438
|
+
# self.sort_column = int(setting[1:])
|
|
1439
|
+
# if len(self.indexed_items):
|
|
1440
|
+
# current_pos = self.indexed_items[self.cursor_pos][0]
|
|
1441
|
+
# 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 items based on new column
|
|
1442
|
+
# if len(self.indexed_items):
|
|
1443
|
+
# new_pos = [row[0] for row in self.indexed_items].index(current_pos)
|
|
1444
|
+
# self.cursor_pos = new_pos
|
|
1355
1445
|
elif setting == "ct":
|
|
1356
1446
|
self.centre_in_terminal = not self.centre_in_terminal
|
|
1357
1447
|
elif setting == "cc":
|
|
@@ -1396,6 +1486,47 @@ class Picker:
|
|
|
1396
1486
|
self.pin_cursor = not self.pin_cursor
|
|
1397
1487
|
elif setting == "unicode":
|
|
1398
1488
|
self.unicode_char_width = not self.unicode_char_width
|
|
1489
|
+
elif setting == "file_next":
|
|
1490
|
+
if len(self.loaded_files) > 1:
|
|
1491
|
+
self.command_stack.append(Command("setting", self.user_settings))
|
|
1492
|
+
# Cache file state
|
|
1493
|
+
self.loaded_file_states[self.loaded_file_index] = self.get_function_data()
|
|
1494
|
+
|
|
1495
|
+
self.loaded_file_index = (self.loaded_file_index + 1) % len(self.loaded_files)
|
|
1496
|
+
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
1497
|
+
|
|
1498
|
+
# If we already have a loaded state for this file
|
|
1499
|
+
if self.loaded_file_states[self.loaded_file_index]:
|
|
1500
|
+
self.set_function_data(self.loaded_file_states[self.loaded_file_index])
|
|
1501
|
+
else:
|
|
1502
|
+
self.set_function_data({}, reset_absent_variables=True)
|
|
1503
|
+
self.load_file(self.loaded_file)
|
|
1504
|
+
|
|
1505
|
+
elif setting == "sheet_next":
|
|
1506
|
+
if not os.path.exists(self.loaded_file):
|
|
1507
|
+
self.notification(self.stdscr, message=f"File {repr(self.loaded_file)} not found.")
|
|
1508
|
+
return None
|
|
1509
|
+
if len(self.sheets) > 1:
|
|
1510
|
+
self.command_stack.append(Command("setting", self.user_settings))
|
|
1511
|
+
|
|
1512
|
+
# Cache sheet state
|
|
1513
|
+
self.sheet_states[self.sheet_index] = self.get_function_data()
|
|
1514
|
+
self.sheet_index = (self.sheet_index + 1) % len(self.sheets)
|
|
1515
|
+
self.sheet_name = self.sheets[self.sheet_index]
|
|
1516
|
+
|
|
1517
|
+
# If we already have a loaded state for this file
|
|
1518
|
+
if self.sheet_states[self.sheet_index]:
|
|
1519
|
+
self.set_function_data(self.sheet_states[self.sheet_index])
|
|
1520
|
+
else:
|
|
1521
|
+
function_data = {
|
|
1522
|
+
"sheet_index": self.sheet_index,
|
|
1523
|
+
"sheet_name": self.sheet_name,
|
|
1524
|
+
"sheet_states":self.sheet_states,
|
|
1525
|
+
"sheets": self.sheets,
|
|
1526
|
+
}
|
|
1527
|
+
self.set_function_data(function_data, reset_absent_variables=True)
|
|
1528
|
+
self.load_sheet(self.loaded_file, sheet_number=self.sheet_index)
|
|
1529
|
+
|
|
1399
1530
|
|
|
1400
1531
|
elif setting.startswith("ft"):
|
|
1401
1532
|
if len(setting) > 2 and setting[2:].isnumeric():
|
|
@@ -1463,9 +1594,9 @@ class Picker:
|
|
|
1463
1594
|
self.user_settings = ""
|
|
1464
1595
|
return None
|
|
1465
1596
|
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1597
|
+
if self.user_settings:
|
|
1598
|
+
self.command_stack.append(Command("setting", self.user_settings))
|
|
1599
|
+
self.user_settings = ""
|
|
1469
1600
|
|
|
1470
1601
|
def apply_command(self, command: Command):
|
|
1471
1602
|
self.logger.info(f"function: apply_command()")
|
|
@@ -1773,17 +1904,41 @@ class Picker:
|
|
|
1773
1904
|
]
|
|
1774
1905
|
|
|
1775
1906
|
if s:
|
|
1776
|
-
|
|
1777
|
-
|
|
1907
|
+
restrict_curses(self.stdscr)
|
|
1908
|
+
files_to_load = file_picker()
|
|
1909
|
+
unrestrict_curses(self.stdscr)
|
|
1910
|
+
if files_to_load:
|
|
1778
1911
|
index = list(s.keys())[0]
|
|
1912
|
+
file_to_load = files_to_load[0]
|
|
1779
1913
|
return_val = funcs[index](file_to_load)
|
|
1780
|
-
self.set_function_data(return_val)
|
|
1781
1914
|
|
|
1915
|
+
self.loaded_file_states[self.loaded_file_index] = self.get_function_data()
|
|
1916
|
+
|
|
1917
|
+
self.stdscr.clear()
|
|
1918
|
+
self.draw_screen(self.indexed_items, self.highlights)
|
|
1919
|
+
|
|
1920
|
+
tmp = self.stdscr
|
|
1921
|
+
|
|
1922
|
+
self.loaded_files += files_to_load
|
|
1923
|
+
self.loaded_file_states += [{} for _ in files_to_load]
|
|
1924
|
+
self.loaded_file = file_to_load
|
|
1925
|
+
self.loaded_file_index = len(self.loaded_files)-len(files_to_load)
|
|
1926
|
+
|
|
1927
|
+
|
|
1928
|
+
self.stdscr = tmp
|
|
1929
|
+
|
|
1930
|
+
h, w = self.stdscr.getmaxyx()
|
|
1931
|
+
self.notification(self.stdscr, f"{repr(file_to_load)} has been loaded!")
|
|
1932
|
+
|
|
1933
|
+
self.set_function_data({}, reset_absent_variables=True)
|
|
1934
|
+
self.load_file(self.loaded_file)
|
|
1782
1935
|
# items = return_val["items"]
|
|
1783
1936
|
# header = return_val["header"]
|
|
1784
|
-
self.
|
|
1937
|
+
self.stdscr.clear()
|
|
1938
|
+
# self.initialise_variables()
|
|
1785
1939
|
self.draw_screen(self.indexed_items, self.highlights)
|
|
1786
|
-
self.
|
|
1940
|
+
# self.stdscr.refresh()
|
|
1941
|
+
|
|
1787
1942
|
# if return_val:
|
|
1788
1943
|
# notification(stdscr, message=return_val, title="Error")
|
|
1789
1944
|
|
|
@@ -1915,6 +2070,36 @@ class Picker:
|
|
|
1915
2070
|
self.cursor_pos = current_cursor_pos
|
|
1916
2071
|
|
|
1917
2072
|
|
|
2073
|
+
def load_file(self, filename: str) -> None:
|
|
2074
|
+
if not os.path.exists(filename):
|
|
2075
|
+
self.notification(self.stdscr, message = f"File not found: {filename}")
|
|
2076
|
+
return None
|
|
2077
|
+
|
|
2078
|
+
filetype = guess_file_type(filename)
|
|
2079
|
+
items, header, sheets = table_to_list(filename, file_type=filetype)
|
|
2080
|
+
|
|
2081
|
+
if items != None:
|
|
2082
|
+
self.items = items
|
|
2083
|
+
self.header = header if header != None else []
|
|
2084
|
+
self.sheets = sheets
|
|
2085
|
+
|
|
2086
|
+
|
|
2087
|
+
self.initialise_variables()
|
|
2088
|
+
|
|
2089
|
+
def load_sheet(self, filename: str, sheet_number: int = 0):
|
|
2090
|
+
filetype = guess_file_type(filename)
|
|
2091
|
+
items, header, sheets = table_to_list(filename, file_type=filetype, sheet_number=sheet_number)
|
|
2092
|
+
|
|
2093
|
+
if items != None:
|
|
2094
|
+
self.items = items
|
|
2095
|
+
self.header = header if header != None else []
|
|
2096
|
+
self.sheets = sheets
|
|
2097
|
+
|
|
2098
|
+
self.initialise_variables()
|
|
2099
|
+
|
|
2100
|
+
|
|
2101
|
+
|
|
2102
|
+
|
|
1918
2103
|
|
|
1919
2104
|
def run(self) -> Tuple[list[int], str, dict]:
|
|
1920
2105
|
""" Run the picker. """
|
|
@@ -1963,7 +2148,7 @@ class Picker:
|
|
|
1963
2148
|
|
|
1964
2149
|
while True:
|
|
1965
2150
|
key = self.stdscr.getch()
|
|
1966
|
-
if key:
|
|
2151
|
+
if key != -1:
|
|
1967
2152
|
self.logger.info(f"key={key}")
|
|
1968
2153
|
h, w = self.stdscr.getmaxyx()
|
|
1969
2154
|
if key in self.disabled_keys: continue
|
|
@@ -2045,9 +2230,24 @@ class Picker:
|
|
|
2045
2230
|
|
|
2046
2231
|
elif self.check_key("exit", key, self.keys_dict):
|
|
2047
2232
|
self.stdscr.clear()
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2233
|
+
if len(self.loaded_files) <= 1:
|
|
2234
|
+
function_data = self.get_function_data()
|
|
2235
|
+
function_data["last_key"] = key
|
|
2236
|
+
return [], "", function_data
|
|
2237
|
+
else:
|
|
2238
|
+
del self.loaded_files[self.loaded_file_index]
|
|
2239
|
+
del self.loaded_file_states[self.loaded_file_index]
|
|
2240
|
+
self.loaded_file_index = min(self.loaded_file_index, len(self.loaded_files)-1)
|
|
2241
|
+
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
2242
|
+
|
|
2243
|
+
|
|
2244
|
+
# If we already have a loaded state for this file
|
|
2245
|
+
if self.loaded_file_states[self.loaded_file_index]:
|
|
2246
|
+
self.set_function_data(self.loaded_file_states[self.loaded_file_index])
|
|
2247
|
+
else:
|
|
2248
|
+
self.set_function_data({}, reset_absent_variables=True)
|
|
2249
|
+
self.load_file(self.loaded_file)
|
|
2250
|
+
|
|
2051
2251
|
elif self.check_key("full_exit", key, self.keys_dict):
|
|
2052
2252
|
close_curses(self.stdscr)
|
|
2053
2253
|
exit()
|
|
@@ -2612,6 +2812,7 @@ class Picker:
|
|
|
2612
2812
|
# 4. if self.cancel_is_back (e.g., notification) then we exit
|
|
2613
2813
|
# 4. selecting
|
|
2614
2814
|
|
|
2815
|
+
pass
|
|
2615
2816
|
# Cancel visual de/selection
|
|
2616
2817
|
if self.is_selecting or self.is_deselecting:
|
|
2617
2818
|
self.start_selection = -1
|
|
@@ -2689,41 +2890,51 @@ class Picker:
|
|
|
2689
2890
|
|
|
2690
2891
|
elif self.check_key("mode_next", key, self.keys_dict): # tab key
|
|
2691
2892
|
self.logger.info(f"key_function mode_next")
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2893
|
+
if len(self.modes):
|
|
2894
|
+
prev_mode_index = self.mode_index
|
|
2895
|
+
self.mode_index = (self.mode_index+1)%len(self.modes)
|
|
2896
|
+
mode = self.modes[self.mode_index]
|
|
2897
|
+
for key, val in mode.items():
|
|
2898
|
+
if key == 'filter':
|
|
2899
|
+
if 'filter' in self.modes[prev_mode_index]:
|
|
2900
|
+
self.filter_query = self.filter_query.replace(self.modes[prev_mode_index]['filter'], '')
|
|
2901
|
+
self.filter_query = f"{self.filter_query.strip()} {val.strip()}".strip()
|
|
2902
|
+
prev_index = self.indexed_items[self.cursor_pos][0] if len(self.indexed_items)>0 else 0
|
|
2903
|
+
|
|
2904
|
+
self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
|
|
2905
|
+
if prev_index in [x[0] for x in self.indexed_items]: new_index = [x[0] for x in self.indexed_items].index(prev_index)
|
|
2906
|
+
else: new_index = 0
|
|
2907
|
+
self.cursor_pos = new_index
|
|
2908
|
+
# Re-sort self.items after applying filter
|
|
2909
|
+
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
|
|
2709
2910
|
elif self.check_key("mode_prev", key, self.keys_dict): # shift+tab key
|
|
2710
2911
|
self.logger.info(f"key_function mode_prev")
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2912
|
+
if len(self.modes):
|
|
2913
|
+
prev_mode_index = self.mode_index
|
|
2914
|
+
self.mode_index = (self.mode_index-1)%len(self.modes)
|
|
2915
|
+
mode = self.modes[self.mode_index]
|
|
2916
|
+
for key, val in mode.items():
|
|
2917
|
+
if key == 'filter':
|
|
2918
|
+
if 'filter' in self.modes[prev_mode_index]:
|
|
2919
|
+
self.filter_query = self.filter_query.replace(self.modes[prev_mode_index]['filter'], '')
|
|
2920
|
+
self.filter_query = f"{self.filter_query.strip()} {val.strip()}".strip()
|
|
2921
|
+
prev_index = self.indexed_items[self.cursor_pos][0] if len(self.indexed_items)>0 else 0
|
|
2922
|
+
self.indexed_items = filter_items(self.items, self.indexed_items, self.filter_query)
|
|
2923
|
+
if prev_index in [x[0] for x in self.indexed_items]: new_index = [x[0] for x in self.indexed_items].index(prev_index)
|
|
2924
|
+
else: new_index = 0
|
|
2925
|
+
self.cursor_pos = new_index
|
|
2926
|
+
# Re-sort self.items after applying filter
|
|
2927
|
+
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
|
|
2928
|
+
elif self.check_key("file_next", key, self.keys_dict):
|
|
2929
|
+
if len(self.loaded_files):
|
|
2930
|
+
self.loaded_file_index = (self.loaded_file_index + 1) % len(self.loaded_files)
|
|
2931
|
+
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
2932
|
+
|
|
2933
|
+
elif self.check_key("file_prev", key, self.keys_dict):
|
|
2934
|
+
if len(self.loaded_files):
|
|
2935
|
+
self.loaded_file_index = (self.loaded_file_index - 1) % len(self.loaded_files)
|
|
2936
|
+
self.loaded_file = self.loaded_files[self.loaded_file_index]
|
|
2937
|
+
|
|
2727
2938
|
elif self.check_key("pipe_input", key, self.keys_dict):
|
|
2728
2939
|
self.logger.info(f"key_function pipe_input")
|
|
2729
2940
|
# usrtxt = "xargs -d '\n' -I{} "
|
|
@@ -3036,7 +3247,8 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
|
|
|
3036
3247
|
""" Parse command line arguments. """
|
|
3037
3248
|
parser = argparse.ArgumentParser(description='Convert table to list of lists.')
|
|
3038
3249
|
# parser.add_argument('filename', type=str, help='The file to process')
|
|
3039
|
-
parser.add_argument('-i', dest='file', help='File containing the table to be converted.')
|
|
3250
|
+
# parser.add_argument('-i', dest='file', help='File containing the table to be converted.')
|
|
3251
|
+
parser.add_argument('-i', dest='file', nargs='+', help='File containing the table to be converted.')
|
|
3040
3252
|
parser.add_argument('--load', '-l', dest='load', type=str, help='Load file from Picker dump.')
|
|
3041
3253
|
parser.add_argument('--stdin', dest='stdin', action='store_true', help='Table passed on stdin')
|
|
3042
3254
|
parser.add_argument('--stdin2', action='store_true', help='Table passed on stdin')
|
|
@@ -3058,7 +3270,8 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
|
|
|
3058
3270
|
}
|
|
3059
3271
|
|
|
3060
3272
|
if args.file:
|
|
3061
|
-
input_arg = args.file
|
|
3273
|
+
input_arg = args.file[0]
|
|
3274
|
+
|
|
3062
3275
|
elif args.stdin:
|
|
3063
3276
|
input_arg = '--stdin'
|
|
3064
3277
|
elif args.stdin2:
|
|
@@ -3096,9 +3309,14 @@ def parse_arguments() -> Tuple[argparse.Namespace, dict]:
|
|
|
3096
3309
|
filetype = args.file_type
|
|
3097
3310
|
|
|
3098
3311
|
|
|
3099
|
-
items, header = table_to_list(input_arg, args.delimiter, filetype)
|
|
3312
|
+
items, header, sheets = table_to_list(input_arg, args.delimiter, filetype)
|
|
3100
3313
|
function_data["items"] = items
|
|
3101
3314
|
if header: function_data["header"] = header
|
|
3315
|
+
function_data["sheets"] = sheets
|
|
3316
|
+
if args.file:
|
|
3317
|
+
function_data["loaded_file"] = args.file[0]
|
|
3318
|
+
function_data["loaded_files"] = args.file
|
|
3319
|
+
|
|
3102
3320
|
return args, function_data
|
|
3103
3321
|
|
|
3104
3322
|
def start_curses() -> curses.window:
|
|
@@ -3188,7 +3406,8 @@ def main() -> None:
|
|
|
3188
3406
|
# function_data["infobox_title"] = "Title"
|
|
3189
3407
|
# function_data["footer_string"] = "Title"
|
|
3190
3408
|
function_data["highlights"] = highlights
|
|
3191
|
-
function_data["show_footer"] = False
|
|
3409
|
+
# function_data["show_footer"] = False
|
|
3410
|
+
# function_data["paginate"] = True
|
|
3192
3411
|
# function_data["debug"] = True
|
|
3193
3412
|
# function_data["debug_level"] = 1
|
|
3194
3413
|
stdscr = start_curses()
|
|
@@ -50,32 +50,109 @@ class StandardFooter(Footer):
|
|
|
50
50
|
def draw(self, h, w):
|
|
51
51
|
state = self.get_state()
|
|
52
52
|
# Fill background
|
|
53
|
-
for i in range(self.height):
|
|
54
|
-
self.stdscr.addstr(h-self.height+i, 0, ' '*(w-1), curses.color_pair(self.colours_start+20))
|
|
55
53
|
|
|
54
|
+
|
|
55
|
+
sheets_y=-1
|
|
56
56
|
if state["footer_string"]:
|
|
57
|
-
footer_string_width = min(w-1, len(state["footer_string"])+2)
|
|
58
57
|
|
|
59
|
-
disp_string = f"{state["footer_string"][:footer_string_width]}"
|
|
60
|
-
disp_string = f" {disp_string:>{footer_string_width-2}} "
|
|
61
|
-
self.stdscr.addstr(h - 1, w-footer_string_width-1, " "*footer_string_width, curses.color_pair(self.colours_start+24))
|
|
62
|
-
self.stdscr.addstr(h - 1, w-footer_string_width-1, f"{disp_string}", curses.color_pair(self.colours_start+24))
|
|
63
58
|
|
|
59
|
+
footer_string_y = h-1
|
|
64
60
|
picker_info_y = h-3
|
|
65
61
|
sort_info_y = h-2
|
|
62
|
+
|
|
66
63
|
self.height = 3
|
|
67
64
|
|
|
68
65
|
else:
|
|
69
66
|
picker_info_y = h-2
|
|
70
67
|
sort_info_y = h-1
|
|
71
|
-
|
|
72
|
-
select_mode = "C"
|
|
73
|
-
if state["is_selecting"]: select_mode = "VS"
|
|
74
|
-
elif state["is_deselecting"]: select_mode = "VDS"
|
|
75
|
-
if state["pin_cursor"]: select_mode = f"{select_mode} "
|
|
76
|
-
self.stdscr.addstr(h - 1, w-35, f"{select_mode:>33} ", curses.color_pair(self.colours_start+20))
|
|
68
|
+
footer_string_y = -1
|
|
77
69
|
self.height = 2
|
|
78
70
|
|
|
71
|
+
if len(state["sheets"]) > 1:
|
|
72
|
+
self.height += 1
|
|
73
|
+
picker_info_y -= 1
|
|
74
|
+
sort_info_y -= 1
|
|
75
|
+
footer_string_y -= 1
|
|
76
|
+
sheets_y = h-1
|
|
77
|
+
|
|
78
|
+
if len(state["loaded_files"]) > 1 and state["loaded_file"] in state["loaded_files"]:
|
|
79
|
+
self.height += 1
|
|
80
|
+
picker_info_y -= 1
|
|
81
|
+
sort_info_y -= 1
|
|
82
|
+
footer_string_y -= 1
|
|
83
|
+
sheets_y -= 1
|
|
84
|
+
|
|
85
|
+
files_y = h-1
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
for i in range(self.height):
|
|
89
|
+
self.stdscr.addstr(h-self.height+i, 0, ' '*(w-1), curses.color_pair(self.colours_start+20))
|
|
90
|
+
|
|
91
|
+
if len(state["loaded_files"]) > 1 and state["loaded_file"] in state["loaded_files"]:
|
|
92
|
+
|
|
93
|
+
sep = "◢ "
|
|
94
|
+
files = [x.split("/")[-1] for x in state["loaded_files"]]
|
|
95
|
+
filename = state["loaded_file"].split("/")[:-1]
|
|
96
|
+
|
|
97
|
+
files_str = sep.join(files)
|
|
98
|
+
files_str = files_str[:w-2]
|
|
99
|
+
|
|
100
|
+
idx = state["loaded_file_index"]
|
|
101
|
+
current_file_x = sum((len(x) for x in files[:idx])) + idx*len(sep)
|
|
102
|
+
current_file_str = state["loaded_file"].split("/")[-1]
|
|
103
|
+
current_file_x_end = current_file_x + len(current_file_str) + 2
|
|
104
|
+
self.stdscr.addstr(files_y, 0, ' '*(w-1), curses.color_pair(self.colours_start+4))
|
|
105
|
+
if current_file_x_end < w:
|
|
106
|
+
|
|
107
|
+
self.stdscr.addstr(files_y, 0, f" {files_str}", curses.color_pair(self.colours_start+4))
|
|
108
|
+
|
|
109
|
+
self.stdscr.addstr(files_y, current_file_x, f" {current_file_str}{sep[0]}", curses.color_pair(self.colours_start+4) | curses.A_REVERSE)
|
|
110
|
+
else:
|
|
111
|
+
files_str = sep.join(files)
|
|
112
|
+
files_str = files_str[current_file_x_end-w:current_file_x_end][:w-2]
|
|
113
|
+
self.stdscr.addstr(files_y, 0, f" {files_str}", curses.color_pair(self.colours_start+4))
|
|
114
|
+
|
|
115
|
+
self.stdscr.addstr(files_y, w - (len(current_file_str)+3), f" {current_file_str}{sep[0]}", curses.color_pair(self.colours_start+4) | curses.A_REVERSE)
|
|
116
|
+
|
|
117
|
+
if len(state["sheets"]) > 1:
|
|
118
|
+
|
|
119
|
+
sep = "◢ "
|
|
120
|
+
sheets = [x.split("/")[-1] for x in state["sheets"]]
|
|
121
|
+
filename = state["sheet_name"].split("/")[:-1]
|
|
122
|
+
|
|
123
|
+
sheets_str = sep.join(sheets)
|
|
124
|
+
sheets_str = sheets_str[:w-2]
|
|
125
|
+
|
|
126
|
+
idx = state["sheet_index"]
|
|
127
|
+
current_sheet_x = sum((len(x) for x in sheets[:idx])) + idx*len(sep)
|
|
128
|
+
current_sheet_str = state["sheet_name"].split("/")[-1]
|
|
129
|
+
current_sheet_x_end = current_sheet_x + len(current_sheet_str) + 2
|
|
130
|
+
self.stdscr.addstr(sheets_y, 0, ' '*(w-1), curses.color_pair(self.colours_start+4))
|
|
131
|
+
if current_sheet_x_end < w:
|
|
132
|
+
|
|
133
|
+
self.stdscr.addstr(sheets_y, 0, f" {sheets_str}", curses.color_pair(self.colours_start+4))
|
|
134
|
+
|
|
135
|
+
self.stdscr.addstr(sheets_y, current_sheet_x, f" {current_sheet_str}{sep[0]}", curses.color_pair(self.colours_start+4) | curses.A_REVERSE)
|
|
136
|
+
else:
|
|
137
|
+
sheets_str = sep.join(sheets)
|
|
138
|
+
sheets_str = sheets_str[current_sheet_x_end-w:current_sheet_x_end][:w-2]
|
|
139
|
+
self.stdscr.addstr(sheets_y, 0, f" {sheets_str}", curses.color_pair(self.colours_start+4))
|
|
140
|
+
|
|
141
|
+
self.stdscr.addstr(sheets_y, w - (len(current_sheet_str)+3), f" {current_sheet_str}{sep[0]}", curses.color_pair(self.colours_start+4) | curses.A_REVERSE)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
if state["footer_string"]:
|
|
147
|
+
footer_string_width = min(w-1, len(state["footer_string"])+2)
|
|
148
|
+
|
|
149
|
+
disp_string = f"{state["footer_string"][:footer_string_width]}"
|
|
150
|
+
disp_string = f" {disp_string:>{footer_string_width-2}} "
|
|
151
|
+
self.stdscr.addstr(footer_string_y, w-footer_string_width-1, " "*footer_string_width, curses.color_pair(self.colours_start+24))
|
|
152
|
+
self.stdscr.addstr(footer_string_y, w-footer_string_width-1, f"{disp_string}", curses.color_pair(self.colours_start+24))
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
|
|
79
156
|
|
|
80
157
|
if state["filter_query"]:
|
|
81
158
|
self.stdscr.addstr(h - 2, 2, f" Filter: {state['filter_query']} "[:w-40], curses.color_pair(self.colours_start+20) | curses.A_BOLD)
|
|
@@ -93,10 +170,14 @@ class StandardFooter(Footer):
|
|
|
93
170
|
# Cursor & selection info
|
|
94
171
|
selected_count = sum(state["selections"].values())
|
|
95
172
|
if state["paginate"]:
|
|
96
|
-
cursor_disp_str = f" [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])}
|
|
173
|
+
# cursor_disp_str = f" [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])} | Page {state['cursor_pos']//state['items_per_page']}/{len(state['indexed_items'])//state['items_per_page']} | {select_mode}"
|
|
174
|
+
cursor_disp_str = f" [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])} | Page {state['cursor_pos']//state['items_per_page']}/{len(state['indexed_items'])//state['items_per_page']} | {select_mode}"
|
|
97
175
|
else:
|
|
98
176
|
cursor_disp_str = f" [{selected_count}] {state['cursor_pos']+1}/{len(state['indexed_items'])} | {select_mode}"
|
|
99
|
-
|
|
177
|
+
|
|
178
|
+
# Maximum chars that should be displayed
|
|
179
|
+
max_chars = min(len(cursor_disp_str)+2, w)
|
|
180
|
+
self.stdscr.addstr(picker_info_y, w-max_chars, f"{cursor_disp_str:>{max_chars-2}} ", curses.color_pair(self.colours_start+20))
|
|
100
181
|
|
|
101
182
|
|
|
102
183
|
# Sort info
|
|
@@ -105,7 +186,8 @@ class StandardFooter(Footer):
|
|
|
105
186
|
sort_order_info = "Desc." if state["sort_reverse"] else "Asc."
|
|
106
187
|
sort_order_info = "▼" if state["sort_reverse"][state['sort_column']] else "▲"
|
|
107
188
|
sort_disp_str = f" Sort: ({sort_column_info}, {sort_method_info}, {sort_order_info}) "
|
|
108
|
-
|
|
189
|
+
max_chars = min(len(sort_disp_str)+2, w)
|
|
190
|
+
self.stdscr.addstr(sort_info_y, w-max_chars, f"{sort_disp_str:>{max_chars-1}}", curses.color_pair(self.colours_start+20))
|
|
109
191
|
|
|
110
192
|
self.stdscr.refresh()
|
|
111
193
|
|
|
@@ -37,10 +37,27 @@ def strip_whitespace(item: Iterable) -> Iterable:
|
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
def table_to_list(
|
|
40
|
+
def table_to_list(
|
|
41
|
+
|
|
42
|
+
input_arg: str,
|
|
43
|
+
delimiter:str='\t',
|
|
44
|
+
file_type:Optional[str]=None,
|
|
45
|
+
sheet_number:int = 0,
|
|
46
|
+
|
|
47
|
+
) -> Tuple[list[list[str]], list[str], list[str]]:
|
|
41
48
|
"""
|
|
42
49
|
Convert data string to list. The input_arg
|
|
43
50
|
Currently accepts: csv, tsv, json, xlsx, ods
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
input_arg: filename
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
returns:
|
|
57
|
+
items: list[list[str]]
|
|
58
|
+
header: list[str]
|
|
59
|
+
sheets: list[str]
|
|
60
|
+
|
|
44
61
|
"""
|
|
45
62
|
logger.info("function: table_to_list (table_to_list_of_lists.py)")
|
|
46
63
|
table_data = []
|
|
@@ -76,10 +93,10 @@ def table_to_list(input_arg: str, delimiter:str='\t', file_type:Optional[str]=No
|
|
|
76
93
|
table_data = csv_string_to_list(input_data)
|
|
77
94
|
table_data = strip_whitespace(table_data)
|
|
78
95
|
# table_data = parse_csv_like(input_data, ",")
|
|
79
|
-
return table_data, []
|
|
96
|
+
return table_data, [], []
|
|
80
97
|
except Exception as e:
|
|
81
98
|
print(f"Error reading CSV/TSV input: {e}")
|
|
82
|
-
return [], []
|
|
99
|
+
return [], [], []
|
|
83
100
|
|
|
84
101
|
elif file_type == 'tsv':
|
|
85
102
|
try:
|
|
@@ -99,10 +116,10 @@ def table_to_list(input_arg: str, delimiter:str='\t', file_type:Optional[str]=No
|
|
|
99
116
|
|
|
100
117
|
table_data = parse_csv_like(input_data, delimiter)
|
|
101
118
|
table_data = strip_whitespace(table_data)
|
|
102
|
-
return table_data, []
|
|
119
|
+
return table_data, [], []
|
|
103
120
|
except Exception as e:
|
|
104
121
|
print(f"Error reading CSV/TSV input: {e}")
|
|
105
|
-
return [], []
|
|
122
|
+
return [], [], []
|
|
106
123
|
|
|
107
124
|
elif file_type == 'json':
|
|
108
125
|
try:
|
|
@@ -115,55 +132,53 @@ def table_to_list(input_arg: str, delimiter:str='\t', file_type:Optional[str]=No
|
|
|
115
132
|
input_data = read_file_content(input_arg)
|
|
116
133
|
|
|
117
134
|
table_data = json.loads(input_data)
|
|
118
|
-
return table_data, []
|
|
135
|
+
return table_data, [], []
|
|
119
136
|
except json.JSONDecodeError as e:
|
|
120
137
|
print(f"Error decoding JSON input: {e}")
|
|
121
|
-
return [], []
|
|
138
|
+
return [], [], []
|
|
122
139
|
except FileNotFoundError as e:
|
|
123
140
|
print(f"File not found: {e}")
|
|
124
|
-
return [], []
|
|
141
|
+
return [], [], []
|
|
125
142
|
|
|
126
143
|
elif file_type == 'xlsx':
|
|
127
|
-
|
|
144
|
+
import pandas as pd
|
|
145
|
+
ef = pd.ExcelFile(input_arg)
|
|
146
|
+
sheets = ef.sheet_names
|
|
147
|
+
if sheet_number < len(sheets):
|
|
148
|
+
df = pd.read_excel(input_arg, sheet_name=sheet_number)
|
|
149
|
+
else:
|
|
150
|
+
df = pd.read_excel(input_arg)
|
|
151
|
+
table_data = df.values.tolist()
|
|
128
152
|
try:
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
elif input_arg == '--stdin2':
|
|
134
|
-
input_count = int(sys.stdin.readline())
|
|
135
|
-
input_data = "\n".join([sys.stdin.readline() for i in range(input_count)])
|
|
136
|
-
with open('temp.xlsx', 'wb') as f:
|
|
137
|
-
f.write(input_data.encode())
|
|
138
|
-
else:
|
|
139
|
-
input_data = read_file_content(input_arg)
|
|
140
|
-
with open('temp.xlsx', 'wb') as f:
|
|
141
|
-
f.write(input_data.encode())
|
|
142
|
-
|
|
143
|
-
wb = load_workbook(filename='temp.xlsx')
|
|
144
|
-
sheet = wb.active
|
|
145
|
-
for row in sheet.iter_rows(values_only=True):
|
|
146
|
-
table_data.append(list(row))
|
|
147
|
-
return table_data, []
|
|
148
|
-
except Exception as e:
|
|
149
|
-
print(f"Error loading Excel file: {e}")
|
|
150
|
-
return [], []
|
|
153
|
+
header = list(df.columns)
|
|
154
|
+
except:
|
|
155
|
+
header = []
|
|
156
|
+
return table_data, header, sheets
|
|
151
157
|
|
|
152
158
|
elif file_type == 'ods':
|
|
153
159
|
try:
|
|
154
160
|
import pandas as pd
|
|
155
|
-
|
|
161
|
+
ef = pd.ExcelFile(input_arg)
|
|
162
|
+
sheets = ef.sheet_names
|
|
163
|
+
if sheet_number < len(sheets):
|
|
164
|
+
df = pd.read_excel(input_arg, engine='odf', sheet_name=sheet_number)
|
|
165
|
+
else:
|
|
166
|
+
df = pd.read_excel(input_arg, engine='odf')
|
|
156
167
|
table_data = df.values.tolist()
|
|
157
|
-
|
|
168
|
+
try:
|
|
169
|
+
header = list(df.columns)
|
|
170
|
+
except:
|
|
171
|
+
header = []
|
|
172
|
+
return table_data, header, sheets
|
|
158
173
|
except Exception as e:
|
|
159
174
|
print(f"Error loading ODS file: {e}")
|
|
160
|
-
return [], []
|
|
175
|
+
return [], [], []
|
|
161
176
|
elif file_type == 'pkl':
|
|
162
177
|
with open(os.path.expandvars(os.path.expanduser(input_arg)), 'rb') as f:
|
|
163
178
|
loaded_data = pickle.load(f)
|
|
164
179
|
items = loaded_data["items"] if "items" in loaded_data else []
|
|
165
180
|
header = loaded_data["header"] if "header" in loaded_data else []
|
|
166
|
-
return items, header
|
|
181
|
+
return items, header, []
|
|
167
182
|
|
|
168
183
|
if input_arg == '--stdin':
|
|
169
184
|
input_data = sys.stdin.read()
|
|
@@ -175,7 +190,7 @@ def table_to_list(input_arg: str, delimiter:str='\t', file_type:Optional[str]=No
|
|
|
175
190
|
|
|
176
191
|
table_data = parse_csv_like(input_data, delimiter)
|
|
177
192
|
|
|
178
|
-
return table_data, []
|
|
193
|
+
return table_data, [], []
|
|
179
194
|
|
|
180
195
|
if __name__ == '__main__':
|
|
181
196
|
parser = argparse.ArgumentParser(description='Convert table to list of lists.')
|
|
@@ -311,7 +311,7 @@ def openFiles(files: list[str]) -> str:
|
|
|
311
311
|
# return result.stderr.read().decode("utf-8").strip()
|
|
312
312
|
return ""
|
|
313
313
|
|
|
314
|
-
def file_picker() -> str:
|
|
314
|
+
def file_picker() -> list[str]:
|
|
315
315
|
""" Run file picker (yazi by default) and return the path of the file picked. If no file is picked an empty string is returned. """
|
|
316
316
|
|
|
317
317
|
logger.info("function: file_picker (utils.py)")
|
|
@@ -320,10 +320,11 @@ def file_picker() -> str:
|
|
|
320
320
|
|
|
321
321
|
lines = tmpfile.readlines()
|
|
322
322
|
if lines:
|
|
323
|
-
|
|
324
|
-
|
|
323
|
+
filenames = [line.decode("utf-8").strip() for line in lines]
|
|
324
|
+
# filename = lines[0].decode("utf-8").strip()
|
|
325
|
+
return filenames
|
|
325
326
|
else:
|
|
326
|
-
return
|
|
327
|
+
return []
|
|
327
328
|
|
|
328
329
|
|
|
329
330
|
def dir_picker() -> str:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: listpick
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.14.0
|
|
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
|
|
@@ -26,6 +26,7 @@ Requires-Dist: pandas; extra == "full"
|
|
|
26
26
|
Requires-Dist: pyperclip; extra == "full"
|
|
27
27
|
Requires-Dist: toml; extra == "full"
|
|
28
28
|
Requires-Dist: traitlets; extra == "full"
|
|
29
|
+
Requires-Dist: odfpy; extra == "full"
|
|
29
30
|
Dynamic: author
|
|
30
31
|
Dynamic: author-email
|
|
31
32
|
Dynamic: classifier
|
|
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
|