kspl 1.2.0__py3-none-any.whl → 1.4.0__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.
- kspl/__init__.py +1 -1
- kspl/config_slurper.py +17 -0
- kspl/gui.py +260 -66
- {kspl-1.2.0.dist-info → kspl-1.4.0.dist-info}/METADATA +25 -9
- kspl-1.4.0.dist-info/RECORD +14 -0
- kspl-1.2.0.dist-info/RECORD +0 -14
- {kspl-1.2.0.dist-info → kspl-1.4.0.dist-info}/LICENSE +0 -0
- {kspl-1.2.0.dist-info → kspl-1.4.0.dist-info}/WHEEL +0 -0
- {kspl-1.2.0.dist-info → kspl-1.4.0.dist-info}/entry_points.txt +0 -0
kspl/__init__.py
CHANGED
@@ -1 +1 @@
|
|
1
|
-
__version__ = "1.
|
1
|
+
__version__ = "1.4.0"
|
kspl/config_slurper.py
CHANGED
@@ -69,3 +69,20 @@ class SPLKConfigData:
|
|
69
69
|
if variant.name == variant_name:
|
70
70
|
return variant
|
71
71
|
return None
|
72
|
+
|
73
|
+
def refresh_data(self) -> None:
|
74
|
+
"""Refresh the KConfig data by reloading all configuration files."""
|
75
|
+
variant_config_files = self._search_variant_config_file(self.project_root_dir)
|
76
|
+
if not self.kconfig_model_file.is_file():
|
77
|
+
raise UserNotificationException(f"File {self.kconfig_model_file} does not exist.")
|
78
|
+
|
79
|
+
# Reload the model
|
80
|
+
self.model = KConfig(self.kconfig_model_file)
|
81
|
+
|
82
|
+
# Reload variant configurations
|
83
|
+
if variant_config_files:
|
84
|
+
self.variant_configs = [VariantData(self._get_variant_name(file), KConfig(self.kconfig_model_file, file)) for file in variant_config_files]
|
85
|
+
else:
|
86
|
+
self.variant_configs = [VariantData("Default", self.model)]
|
87
|
+
|
88
|
+
self.logger.info(f"Refreshed data: found {len(self.variant_configs)} variants")
|
kspl/gui.py
CHANGED
@@ -21,6 +21,7 @@ from kspl.kconfig import ConfigElementType, EditableConfigElement, TriState
|
|
21
21
|
|
22
22
|
class KSplEvents(EventID):
|
23
23
|
EDIT = auto()
|
24
|
+
REFRESH = auto()
|
24
25
|
|
25
26
|
|
26
27
|
class CTkView(View):
|
@@ -47,12 +48,11 @@ class MainView(CTkView):
|
|
47
48
|
self.elements = elements
|
48
49
|
self.elements_dict = {elem.name: elem for elem in elements}
|
49
50
|
self.variants = variants
|
50
|
-
self.all_columns = [v.name for v in self.variants]
|
51
|
-
self.visible_columns = list(self.all_columns)
|
52
51
|
|
53
52
|
self.logger = logger.bind()
|
54
53
|
self.edit_event_data: EditEventData | None = None
|
55
54
|
self.trigger_edit_event = self.event_manager.create_event_trigger(KSplEvents.EDIT)
|
55
|
+
self.trigger_refresh_event = self.event_manager.create_event_trigger(KSplEvents.REFRESH)
|
56
56
|
self.root = customtkinter.CTk()
|
57
57
|
|
58
58
|
# Configure the main window
|
@@ -63,28 +63,36 @@ class MainView(CTkView):
|
|
63
63
|
control_frame = customtkinter.CTkFrame(self.root)
|
64
64
|
control_frame.grid(row=0, column=0, sticky="ew", padx=10, pady=5)
|
65
65
|
|
66
|
-
|
66
|
+
# Define control actions - only need to maintain this list
|
67
|
+
self.control_actions = [
|
68
|
+
("🔽 Expand", self.expand_all_items),
|
69
|
+
("🔼 Collapse", self.collapse_all_items),
|
70
|
+
("🔍 Select", self.open_column_selection_dialog),
|
71
|
+
("🔄 Refresh", self.trigger_refresh_event),
|
72
|
+
]
|
73
|
+
|
74
|
+
# Tree expansion controls with segmented button including filter
|
75
|
+
self.tree_control_segment = customtkinter.CTkSegmentedButton(
|
67
76
|
master=control_frame,
|
68
|
-
|
69
|
-
command=self.
|
77
|
+
values=[action[0] for action in self.control_actions],
|
78
|
+
command=self.on_tree_control_segment_click,
|
79
|
+
height=35,
|
80
|
+
font=("Arial", 14),
|
70
81
|
)
|
71
|
-
self.
|
82
|
+
self.tree_control_segment.pack(side="left", padx=2)
|
72
83
|
|
73
84
|
# ========================================================
|
74
85
|
# create main content frame
|
75
86
|
main_frame = customtkinter.CTkFrame(self.root)
|
76
87
|
self.tree = self.create_tree_view(main_frame)
|
77
|
-
|
78
|
-
|
79
|
-
self.
|
80
|
-
self.
|
81
|
-
|
82
|
-
self.tree.heading(variant.name, text=variant.name)
|
83
|
-
self.header_texts[variant.name] = variant.name
|
88
|
+
|
89
|
+
# Initialize column manager after tree is created
|
90
|
+
self.column_manager = ColumnManager(self.tree)
|
91
|
+
self.column_manager.update_columns(self.variants)
|
92
|
+
|
84
93
|
# Keep track of the mapping between the tree view items and the config elements
|
85
94
|
self.tree_view_items_mapping = self.populate_tree_view()
|
86
95
|
self.adjust_column_width()
|
87
|
-
self.selected_column_id: str | None = None
|
88
96
|
self.tree.bind("<Button-1>", self.on_tree_click)
|
89
97
|
# TODO: make the tree view editable
|
90
98
|
# self.tree.bind("<Double-1>", self.double_click_handler)
|
@@ -217,56 +225,48 @@ class MainView(CTkView):
|
|
217
225
|
"""Adjust the column widths to fit the header text, preserving manual resizing."""
|
218
226
|
heading_font = font.Font(font=("Calibri", 14, "bold"))
|
219
227
|
padding = 60
|
220
|
-
|
221
|
-
|
228
|
+
|
229
|
+
# Only adjust columns that actually exist in the current configuration
|
230
|
+
current_columns = self.tree["columns"]
|
231
|
+
for col in current_columns:
|
232
|
+
try:
|
233
|
+
text = self.tree.heading(col, "text")
|
234
|
+
min_width = heading_font.measure(text) + padding
|
235
|
+
# Get current width to preserve manual resizing
|
236
|
+
current_width = self.tree.column(col, "width")
|
237
|
+
# Use the larger of current width or minimum required width
|
238
|
+
final_width = max(current_width, min_width)
|
239
|
+
self.tree.column(col, minwidth=min_width, width=final_width, stretch=False)
|
240
|
+
except tkinter.TclError:
|
241
|
+
# Column might not exist anymore, skip it
|
242
|
+
self.logger.warning(f"Skipping column '{col}' as it no longer exists")
|
243
|
+
continue
|
244
|
+
|
245
|
+
# First column (#0)
|
246
|
+
try:
|
247
|
+
text = self.tree.heading("#0", "text")
|
222
248
|
min_width = heading_font.measure(text) + padding
|
223
|
-
|
224
|
-
current_width = self.tree.column(col, "width")
|
225
|
-
# Use the larger of current width or minimum required width
|
249
|
+
current_width = self.tree.column("#0", "width")
|
226
250
|
final_width = max(current_width, min_width)
|
227
|
-
self.tree.column(
|
228
|
-
|
229
|
-
|
230
|
-
min_width = heading_font.measure(text) + padding
|
231
|
-
current_width = self.tree.column("#0", "width")
|
232
|
-
final_width = max(current_width, min_width)
|
233
|
-
self.tree.column("#0", minwidth=min_width, width=final_width, stretch=False)
|
251
|
+
self.tree.column("#0", minwidth=min_width, width=final_width, stretch=False)
|
252
|
+
except tkinter.TclError:
|
253
|
+
self.logger.warning("Skipping column '#0' as it no longer exists")
|
234
254
|
|
235
255
|
def on_tree_click(self, event: Any) -> None:
|
236
256
|
"""Handle click events on the treeview to highlight the column header."""
|
237
|
-
|
238
|
-
if not column_id_str or column_id_str == "#0":
|
239
|
-
# Click was on the tree part or outside columns, so reset if anything was selected
|
240
|
-
if self.selected_column_id:
|
241
|
-
original_text = self.header_texts.get(self.selected_column_id)
|
242
|
-
if original_text:
|
243
|
-
self.tree.heading(self.selected_column_id, text=original_text)
|
244
|
-
self.selected_column_id = None
|
245
|
-
return
|
257
|
+
column_name = self.column_manager.get_column_from_click_position(event.x)
|
246
258
|
|
247
|
-
|
248
|
-
|
259
|
+
if column_name is None:
|
260
|
+
# Click was on the tree part or outside columns, clear selection
|
261
|
+
self.column_manager.clear_selection()
|
249
262
|
return
|
250
|
-
# Use displaycolumns instead of columns to account for hidden columns
|
251
|
-
visible_columns = self.tree["displaycolumns"]
|
252
|
-
if col_idx >= len(visible_columns):
|
253
|
-
return
|
254
|
-
column_name = visible_columns[col_idx]
|
255
263
|
|
256
|
-
if column_name == self.selected_column_id:
|
264
|
+
if column_name == self.column_manager.selected_column_id:
|
265
|
+
# Already selected, do nothing
|
257
266
|
return
|
258
267
|
|
259
|
-
#
|
260
|
-
|
261
|
-
original_text = self.header_texts.get(self.selected_column_id)
|
262
|
-
if original_text:
|
263
|
-
self.tree.heading(self.selected_column_id, text=original_text)
|
264
|
-
|
265
|
-
# Highlight the new column header
|
266
|
-
original_text = self.header_texts.get(column_name)
|
267
|
-
if original_text:
|
268
|
-
self.tree.heading(column_name, text=f"✅{original_text}")
|
269
|
-
self.selected_column_id = column_name
|
268
|
+
# Set the new selected column
|
269
|
+
self.column_manager.set_selected_column(column_name)
|
270
270
|
|
271
271
|
def double_click_handler(self, event: Any) -> None:
|
272
272
|
current_selection = self.tree.selection()
|
@@ -324,11 +324,43 @@ class MainView(CTkView):
|
|
324
324
|
self.edit_event_data = None
|
325
325
|
return result
|
326
326
|
|
327
|
-
def
|
328
|
-
"""
|
329
|
-
|
330
|
-
|
331
|
-
|
327
|
+
def expand_all_items(self) -> None:
|
328
|
+
"""Expand all items in the tree view."""
|
329
|
+
|
330
|
+
def expand_recursive(item: str) -> None:
|
331
|
+
self.tree.item(item, open=True)
|
332
|
+
children = self.tree.get_children(item)
|
333
|
+
for child in children:
|
334
|
+
expand_recursive(child)
|
335
|
+
|
336
|
+
# Start with root items
|
337
|
+
root_items = self.tree.get_children()
|
338
|
+
for item in root_items:
|
339
|
+
expand_recursive(item)
|
340
|
+
|
341
|
+
def collapse_all_items(self) -> None:
|
342
|
+
"""Collapse all items in the tree view."""
|
343
|
+
|
344
|
+
def collapse_recursive(item: str) -> None:
|
345
|
+
children = self.tree.get_children(item)
|
346
|
+
for child in children:
|
347
|
+
collapse_recursive(child)
|
348
|
+
self.tree.item(item, open=False)
|
349
|
+
|
350
|
+
# Start with root items
|
351
|
+
root_items = self.tree.get_children()
|
352
|
+
for item in root_items:
|
353
|
+
collapse_recursive(item)
|
354
|
+
|
355
|
+
def on_tree_control_segment_click(self, value: str) -> None:
|
356
|
+
"""Handle clicks on the tree control segmented button."""
|
357
|
+
# Find the action based on the button text and execute it
|
358
|
+
for label, action in self.control_actions:
|
359
|
+
if value == label:
|
360
|
+
action()
|
361
|
+
break
|
362
|
+
# Reset selection to avoid button staying selected
|
363
|
+
self.tree_control_segment.set("")
|
332
364
|
|
333
365
|
def open_column_selection_dialog(self) -> None:
|
334
366
|
"""Open a dialog to select which columns to display."""
|
@@ -341,20 +373,24 @@ class MainView(CTkView):
|
|
341
373
|
checkbox_frame = customtkinter.CTkFrame(dialog)
|
342
374
|
checkbox_frame.pack(padx=10, pady=10, fill="both", expand=True)
|
343
375
|
|
344
|
-
# Create a variable for each column
|
345
|
-
self.
|
346
|
-
for column_name in self.all_columns:
|
376
|
+
# Create a variable for each column using ColumnManager
|
377
|
+
for column_name in self.column_manager.all_columns:
|
347
378
|
# Set the initial value based on whether the column is currently visible
|
348
|
-
is_visible = column_name in self.visible_columns
|
349
|
-
|
379
|
+
is_visible = column_name in self.column_manager.visible_columns
|
380
|
+
|
381
|
+
# Get or create variable
|
382
|
+
if column_name not in self.column_manager.column_vars:
|
383
|
+
self.column_manager.column_vars[column_name] = tkinter.BooleanVar(value=is_visible)
|
384
|
+
else:
|
385
|
+
self.column_manager.column_vars[column_name].set(is_visible)
|
386
|
+
|
350
387
|
checkbox = customtkinter.CTkCheckBox(
|
351
388
|
master=checkbox_frame,
|
352
389
|
text=column_name,
|
353
390
|
command=self.update_visible_columns,
|
354
|
-
variable=
|
391
|
+
variable=self.column_manager.column_vars[column_name],
|
355
392
|
)
|
356
393
|
checkbox.pack(anchor="w", padx=5, pady=2)
|
357
|
-
self.column_vars[column_name] = var
|
358
394
|
|
359
395
|
# Add OK and Cancel buttons
|
360
396
|
button_frame = customtkinter.CTkFrame(dialog)
|
@@ -383,11 +419,35 @@ class MainView(CTkView):
|
|
383
419
|
dialog.transient(self.root) # Keep the dialog above the main window
|
384
420
|
dialog.grab_set() # Make the dialog modal
|
385
421
|
|
422
|
+
def update_visible_columns(self) -> None:
|
423
|
+
"""Wrapper method to update visible columns via ColumnManager."""
|
424
|
+
self.column_manager.update_visible_columns()
|
425
|
+
|
426
|
+
def update_data(self, elements: list[EditableConfigElement], variants: list[VariantViewData]) -> None:
|
427
|
+
"""Update the view with refreshed data."""
|
428
|
+
self.elements = elements
|
429
|
+
self.elements_dict = {elem.name: elem for elem in elements}
|
430
|
+
self.variants = variants
|
431
|
+
|
432
|
+
# Clear the tree first
|
433
|
+
for item in self.tree.get_children():
|
434
|
+
self.tree.delete(item)
|
435
|
+
|
436
|
+
# Use ColumnManager to handle all column-related updates
|
437
|
+
self.column_manager.update_columns(variants)
|
438
|
+
|
439
|
+
# Repopulate the tree view
|
440
|
+
self.tree_view_items_mapping = self.populate_tree_view()
|
441
|
+
self.adjust_column_width()
|
442
|
+
|
443
|
+
# ...existing code...
|
444
|
+
|
386
445
|
|
387
446
|
class KSPL(Presenter):
|
388
447
|
def __init__(self, event_manager: EventManager, project_dir: Path) -> None:
|
389
448
|
self.event_manager = event_manager
|
390
449
|
self.event_manager.subscribe(KSplEvents.EDIT, self.edit)
|
450
|
+
self.event_manager.subscribe(KSplEvents.REFRESH, self.refresh)
|
391
451
|
self.logger = logger.bind()
|
392
452
|
self.kconfig_data = SPLKConfigData(project_dir)
|
393
453
|
self.view = MainView(
|
@@ -411,6 +471,31 @@ class KSPL(Presenter):
|
|
411
471
|
raise ValueError(f"Could not find config element '{edit_event_data.config_element_name}'")
|
412
472
|
config_element.value = edit_event_data.new_value
|
413
473
|
|
474
|
+
def refresh(self) -> None:
|
475
|
+
"""Handle refresh event by reloading data and updating the view."""
|
476
|
+
self.logger.info("Refreshing KConfig data...")
|
477
|
+
try:
|
478
|
+
# Store old state for debugging
|
479
|
+
old_variants = [v.name for v in self.kconfig_data.get_variants()]
|
480
|
+
self.logger.debug(f"Before refresh: {len(old_variants)} variants: {old_variants}")
|
481
|
+
|
482
|
+
self.kconfig_data.refresh_data()
|
483
|
+
|
484
|
+
# Log new state
|
485
|
+
new_variants = [v.name for v in self.kconfig_data.get_variants()]
|
486
|
+
self.logger.debug(f"After refresh: {len(new_variants)} variants: {new_variants}")
|
487
|
+
|
488
|
+
# Update the view with new data
|
489
|
+
self.view.update_data(
|
490
|
+
self.kconfig_data.get_elements(),
|
491
|
+
self.kconfig_data.get_variants(),
|
492
|
+
)
|
493
|
+
self.logger.info("Data refreshed successfully")
|
494
|
+
except Exception as e:
|
495
|
+
self.logger.error(f"Failed to refresh data: {e}")
|
496
|
+
# Don't re-raise the exception to prevent the GUI from crashing
|
497
|
+
# Instead, keep the current data state
|
498
|
+
|
414
499
|
def run(self) -> None:
|
415
500
|
self.view.mainloop()
|
416
501
|
|
@@ -442,3 +527,112 @@ class GuiCommand(Command):
|
|
442
527
|
|
443
528
|
def _register_arguments(self, parser: ArgumentParser) -> None:
|
444
529
|
register_arguments_for_config_dataclass(parser, GuiCommandConfig)
|
530
|
+
|
531
|
+
|
532
|
+
class ColumnManager:
|
533
|
+
"""Manages column state, visibility, selection, and headings for the treeview."""
|
534
|
+
|
535
|
+
def __init__(self, tree: ttk.Treeview) -> None:
|
536
|
+
self.tree = tree
|
537
|
+
self.all_columns: list[str] = []
|
538
|
+
self.visible_columns: list[str] = []
|
539
|
+
self.header_texts: dict[str, str] = {}
|
540
|
+
self.selected_column_id: str | None = None
|
541
|
+
self.column_vars: dict[str, tkinter.BooleanVar] = {}
|
542
|
+
|
543
|
+
def update_columns(self, variants: list[VariantViewData]) -> None:
|
544
|
+
"""Update column configuration with new variants."""
|
545
|
+
# Clear any existing selection FIRST, before any tree operations
|
546
|
+
self.selected_column_id = None
|
547
|
+
|
548
|
+
# Update column lists
|
549
|
+
new_all_columns = [v.name for v in variants]
|
550
|
+
|
551
|
+
# Preserve visible columns that still exist, add new ones
|
552
|
+
if self.visible_columns:
|
553
|
+
existing_visible = [col for col in self.visible_columns if col in new_all_columns]
|
554
|
+
new_columns = [col for col in new_all_columns if col not in existing_visible]
|
555
|
+
self.visible_columns = existing_visible + new_columns
|
556
|
+
else:
|
557
|
+
self.visible_columns = list(new_all_columns)
|
558
|
+
|
559
|
+
# Update all_columns after determining visible columns
|
560
|
+
self.all_columns = new_all_columns
|
561
|
+
|
562
|
+
# Update tree configuration with error handling
|
563
|
+
try:
|
564
|
+
# First completely clear the tree configuration
|
565
|
+
self.tree.configure(columns=(), displaycolumns=())
|
566
|
+
# Then set new configuration
|
567
|
+
self.tree["columns"] = tuple(self.all_columns)
|
568
|
+
self.tree["displaycolumns"] = self.visible_columns
|
569
|
+
except tkinter.TclError:
|
570
|
+
# If there's still an error, log it but continue
|
571
|
+
pass
|
572
|
+
|
573
|
+
# Update header texts and tree headings
|
574
|
+
self.header_texts = {}
|
575
|
+
for variant in variants:
|
576
|
+
self.header_texts[variant.name] = variant.name
|
577
|
+
try:
|
578
|
+
self.tree.heading(variant.name, text=variant.name)
|
579
|
+
except tkinter.TclError:
|
580
|
+
# Column might not exist yet, will be handled in next update
|
581
|
+
pass
|
582
|
+
|
583
|
+
# Clean up column variables for dialog
|
584
|
+
self.column_vars = {k: v for k, v in self.column_vars.items() if k in self.all_columns}
|
585
|
+
|
586
|
+
def set_selected_column(self, column_name: str) -> bool:
|
587
|
+
"""Set the selected column and update its visual state. Returns True if successful."""
|
588
|
+
if column_name not in self.all_columns:
|
589
|
+
return False
|
590
|
+
|
591
|
+
# Clear previous selection
|
592
|
+
self._clear_selection()
|
593
|
+
|
594
|
+
# Set new selection
|
595
|
+
self.selected_column_id = column_name
|
596
|
+
original_text = self.header_texts.get(column_name)
|
597
|
+
if original_text:
|
598
|
+
try:
|
599
|
+
self.tree.heading(column_name, text=f"✅{original_text}")
|
600
|
+
return True
|
601
|
+
except tkinter.TclError:
|
602
|
+
self.selected_column_id = None
|
603
|
+
return False
|
604
|
+
return False
|
605
|
+
|
606
|
+
def clear_selection(self) -> None:
|
607
|
+
"""Clear the column selection."""
|
608
|
+
self._clear_selection()
|
609
|
+
|
610
|
+
def _clear_selection(self) -> None:
|
611
|
+
"""Internal method to clear selection without public access."""
|
612
|
+
if self.selected_column_id and self.selected_column_id in self.header_texts:
|
613
|
+
# Only try to clear if the column still exists in the tree
|
614
|
+
if self.selected_column_id in self.all_columns:
|
615
|
+
original_text = self.header_texts[self.selected_column_id]
|
616
|
+
try:
|
617
|
+
self.tree.heading(self.selected_column_id, text=original_text)
|
618
|
+
except tkinter.TclError:
|
619
|
+
# Column no longer exists in tree, that's fine
|
620
|
+
pass
|
621
|
+
self.selected_column_id = None
|
622
|
+
|
623
|
+
def get_column_from_click_position(self, x: int) -> str | None:
|
624
|
+
"""Get column name from click position, returns None if invalid."""
|
625
|
+
column_id_str = self.tree.identify_column(x)
|
626
|
+
if not column_id_str or column_id_str == "#0":
|
627
|
+
return None
|
628
|
+
|
629
|
+
col_idx = int(column_id_str.replace("#", "")) - 1
|
630
|
+
if col_idx < 0 or col_idx >= len(self.visible_columns):
|
631
|
+
return None
|
632
|
+
|
633
|
+
return self.visible_columns[col_idx]
|
634
|
+
|
635
|
+
def update_visible_columns(self) -> None:
|
636
|
+
"""Update visible columns based on column_vars state."""
|
637
|
+
self.visible_columns = [col_name for col_name, var in self.column_vars.items() if var.get()]
|
638
|
+
self.tree["displaycolumns"] = self.visible_columns
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: kspl
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.4.0
|
4
4
|
Summary: KConfig GUI for Software Product Lines with multiple variants.
|
5
5
|
License: MIT
|
6
6
|
Author: Cuinixam
|
@@ -41,6 +41,9 @@ Description-Content-Type: text/markdown
|
|
41
41
|
<a href="https://github.com/astral-sh/ruff">
|
42
42
|
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json" alt="Ruff">
|
43
43
|
</a>
|
44
|
+
<a href="https://github.com/cuinixam/pypeline">
|
45
|
+
<img src="https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/cuinixam/pypeline/refs/heads/main/assets/badge/v0.json" alt="pypeline">
|
46
|
+
</a>
|
44
47
|
<a href="https://github.com/pre-commit/pre-commit">
|
45
48
|
<img src="https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=flat-square" alt="pre-commit">
|
46
49
|
</a>
|
@@ -61,20 +64,33 @@ Description-Content-Type: text/markdown
|
|
61
64
|
|
62
65
|
KConfig GUI for Software Product Lines with multiple variants.
|
63
66
|
|
64
|
-
##
|
65
|
-
|
66
|
-
Install this via pip (or your favourite package manager):
|
67
|
+
## Start developing
|
67
68
|
|
68
|
-
|
69
|
+
The project uses UV for dependencies management and packaging and the [pypeline](https://github.com/cuinixam/pypeline) for streamlining the development workflow.
|
70
|
+
Use pipx (or your favorite package manager) to install the `pypeline` in an isolated environment:
|
69
71
|
|
70
|
-
|
72
|
+
```shell
|
73
|
+
pipx install pypeline-runner
|
74
|
+
```
|
71
75
|
|
72
|
-
|
76
|
+
To bootstrap the project and run all the steps configured in the `pypeline.yaml` file, execute the following command:
|
73
77
|
|
74
|
-
```
|
75
|
-
|
78
|
+
```shell
|
79
|
+
pypeline run
|
76
80
|
```
|
77
81
|
|
82
|
+
For those using [VS Code](https://code.visualstudio.com/) there are tasks defined for the most common commands:
|
83
|
+
|
84
|
+
- run tests
|
85
|
+
- run pre-commit checks (linters, formatters, etc.)
|
86
|
+
- generate documentation
|
87
|
+
|
88
|
+
See the `.vscode/tasks.json` for more details.
|
89
|
+
|
90
|
+
## Committing changes
|
91
|
+
|
92
|
+
This repository uses [commitlint](https://github.com/conventional-changelog/commitlint) for checking if the commit message meets the [conventional commit format](https://www.conventionalcommits.org/en).
|
93
|
+
|
78
94
|
## Contributors ✨
|
79
95
|
|
80
96
|
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
@@ -0,0 +1,14 @@
|
|
1
|
+
kspl/__init__.py,sha256=8UhoYEXHs1Oai7BW_ExBmuwWnRI-yMG_u1fQAXMizHQ,22
|
2
|
+
kspl/_run.py,sha256=0X7DbSk5DYP2uU0hRIf0XetUNerrABit5U4AdG8wE00,338
|
3
|
+
kspl/config_slurper.py,sha256=co7nTtSznEMQ2uFv5L7O_cUDnDSoIXxGk9PKsdRAAjI,3407
|
4
|
+
kspl/edit.py,sha256=AVy4obDx_FJvHT_Qr0HSPpUcJeiz13gXdsjBTB8d5AU,3156
|
5
|
+
kspl/generate.py,sha256=pcRqgJDZ5gmCwnF_PcwczV_e-w4HK9CLh5ra82HCf4M,6831
|
6
|
+
kspl/gui.py,sha256=Km53yEwepYvJMsGcHKJWUTuDDKepGF64t0rFN7ePTcM,25929
|
7
|
+
kspl/kconfig.py,sha256=eVZKuOTAG7bRzsTfinDq940gWTArxnLKA1YRaYT6kVs,8707
|
8
|
+
kspl/main.py,sha256=U_zVxCcJLqFILSeiNqHNX3adraVAEkKaCAerRqiHF6A,1007
|
9
|
+
kspl/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
kspl-1.4.0.dist-info/LICENSE,sha256=eJwI5n6lDPIjogNcYBln7NKGtreVlek905oBBY_HnxQ,1066
|
11
|
+
kspl-1.4.0.dist-info/METADATA,sha256=KwHmVrICqWsdVHlOVOX15SP8llpbinyrlsIqfMffnW0,4842
|
12
|
+
kspl-1.4.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
13
|
+
kspl-1.4.0.dist-info/entry_points.txt,sha256=6haBy5AuYGJblw1cWUTx5N41Ilg4YALABhi1psEvruY,39
|
14
|
+
kspl-1.4.0.dist-info/RECORD,,
|
kspl-1.2.0.dist-info/RECORD
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
kspl/__init__.py,sha256=MpAT5hgNoHnTtG1XRD_GV_A7QrHVU6vJjGSw_8qMGA4,22
|
2
|
-
kspl/_run.py,sha256=0X7DbSk5DYP2uU0hRIf0XetUNerrABit5U4AdG8wE00,338
|
3
|
-
kspl/config_slurper.py,sha256=Fqr46saO0M74EMZeTqBquYnbll5DwuOysM8eBJSew1A,2578
|
4
|
-
kspl/edit.py,sha256=AVy4obDx_FJvHT_Qr0HSPpUcJeiz13gXdsjBTB8d5AU,3156
|
5
|
-
kspl/generate.py,sha256=pcRqgJDZ5gmCwnF_PcwczV_e-w4HK9CLh5ra82HCf4M,6831
|
6
|
-
kspl/gui.py,sha256=qE0SZX7J-HJndDyO-G6FEBh-JQbQYdDFNHdq_MrH8QU,18250
|
7
|
-
kspl/kconfig.py,sha256=eVZKuOTAG7bRzsTfinDq940gWTArxnLKA1YRaYT6kVs,8707
|
8
|
-
kspl/main.py,sha256=U_zVxCcJLqFILSeiNqHNX3adraVAEkKaCAerRqiHF6A,1007
|
9
|
-
kspl/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
-
kspl-1.2.0.dist-info/LICENSE,sha256=eJwI5n6lDPIjogNcYBln7NKGtreVlek905oBBY_HnxQ,1066
|
11
|
-
kspl-1.2.0.dist-info/METADATA,sha256=OO8QyKs1mXUWG0SD_i2aNBE_fRMJwS5KEc4OpC-XPBA,3837
|
12
|
-
kspl-1.2.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
13
|
-
kspl-1.2.0.dist-info/entry_points.txt,sha256=6haBy5AuYGJblw1cWUTx5N41Ilg4YALABhi1psEvruY,39
|
14
|
-
kspl-1.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|