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 CHANGED
@@ -1 +1 @@
1
- __version__ = "1.2.0"
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
- self.column_select_button = customtkinter.CTkButton(
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
- text="Select variants",
69
- command=self.open_column_selection_dialog,
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.column_select_button.pack(side="left", padx=5)
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
- self.tree["columns"] = tuple(variant.name for variant in self.variants)
78
- self.tree["displaycolumns"] = self.visible_columns
79
- self.tree.heading("#0", text="Configuration")
80
- self.header_texts: dict[str, str] = {}
81
- for variant in self.variants:
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
- for col in self.tree["columns"]:
221
- text = self.tree.heading(col, "text")
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
- # Get current width to preserve manual resizing
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(col, minwidth=min_width, width=final_width, stretch=False)
228
- # First column (#0)
229
- text = self.tree.heading("#0", "text")
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
- column_id_str = self.tree.identify_column(event.x)
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
- col_idx = int(column_id_str.replace("#", "")) - 1
248
- if col_idx < 0:
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
- # Reset the previously selected column header
260
- if self.selected_column_id:
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 update_visible_columns(self) -> None:
328
- """Update the visible columns based on the state of the checkboxes."""
329
- self.visible_columns = [col_name for col_name, var in self.column_vars.items() if var.get()]
330
- self.tree["displaycolumns"] = self.visible_columns
331
- self.adjust_column_width()
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.column_vars = {}
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
- var = tkinter.BooleanVar(value=is_visible)
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=var,
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.2.0
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
- ## Installation
65
-
66
- Install this via pip (or your favourite package manager):
67
+ ## Start developing
67
68
 
68
- `pip install kspl`
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
- ## Usage
72
+ ```shell
73
+ pipx install pypeline-runner
74
+ ```
71
75
 
72
- Start by importing it:
76
+ To bootstrap the project and run all the steps configured in the `pypeline.yaml` file, execute the following command:
73
77
 
74
- ```python
75
- import kspl
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,,
@@ -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