kspl 1.2.0__py3-none-any.whl → 1.3.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.3.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
@@ -70,21 +70,42 @@ class MainView(CTkView):
70
70
  )
71
71
  self.column_select_button.pack(side="left", padx=5)
72
72
 
73
+ # Tree expansion controls with segmented button
74
+ tree_label = customtkinter.CTkLabel(control_frame, text="Tree:", font=("Arial", 12))
75
+ tree_label.pack(side="left", padx=(10, 5))
76
+
77
+ self.tree_control_segment = customtkinter.CTkSegmentedButton(
78
+ master=control_frame,
79
+ values=["⊞", "⊟", "🔄"],
80
+ command=self.on_tree_control_segment_click,
81
+ height=30,
82
+ )
83
+ self.tree_control_segment.pack(side="left", padx=2)
84
+ # Note: Tooltip doesn't work with segmented buttons due to CTk limitations
85
+
86
+ # Reserve space for future refresh button
87
+ # self.refresh_button = customtkinter.CTkButton(
88
+ # master=control_frame,
89
+ # text="🔄",
90
+ # command=self.refresh_data,
91
+ # width=30,
92
+ # height=30,
93
+ # )
94
+ # self.refresh_button.pack(side="left", padx=2)
95
+ # self.create_tooltip(self.refresh_button, "Refresh")
96
+
73
97
  # ========================================================
74
98
  # create main content frame
75
99
  main_frame = customtkinter.CTkFrame(self.root)
76
100
  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
101
+
102
+ # Initialize column manager after tree is created
103
+ self.column_manager = ColumnManager(self.tree)
104
+ self.column_manager.update_columns(self.variants)
105
+
84
106
  # Keep track of the mapping between the tree view items and the config elements
85
107
  self.tree_view_items_mapping = self.populate_tree_view()
86
108
  self.adjust_column_width()
87
- self.selected_column_id: str | None = None
88
109
  self.tree.bind("<Button-1>", self.on_tree_click)
89
110
  # TODO: make the tree view editable
90
111
  # self.tree.bind("<Double-1>", self.double_click_handler)
@@ -217,56 +238,48 @@ class MainView(CTkView):
217
238
  """Adjust the column widths to fit the header text, preserving manual resizing."""
218
239
  heading_font = font.Font(font=("Calibri", 14, "bold"))
219
240
  padding = 60
220
- for col in self.tree["columns"]:
221
- text = self.tree.heading(col, "text")
241
+
242
+ # Only adjust columns that actually exist in the current configuration
243
+ current_columns = self.tree["columns"]
244
+ for col in current_columns:
245
+ try:
246
+ text = self.tree.heading(col, "text")
247
+ min_width = heading_font.measure(text) + padding
248
+ # Get current width to preserve manual resizing
249
+ current_width = self.tree.column(col, "width")
250
+ # Use the larger of current width or minimum required width
251
+ final_width = max(current_width, min_width)
252
+ self.tree.column(col, minwidth=min_width, width=final_width, stretch=False)
253
+ except tkinter.TclError:
254
+ # Column might not exist anymore, skip it
255
+ self.logger.warning(f"Skipping column '{col}' as it no longer exists")
256
+ continue
257
+
258
+ # First column (#0)
259
+ try:
260
+ text = self.tree.heading("#0", "text")
222
261
  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
262
+ current_width = self.tree.column("#0", "width")
226
263
  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)
264
+ self.tree.column("#0", minwidth=min_width, width=final_width, stretch=False)
265
+ except tkinter.TclError:
266
+ self.logger.warning("Skipping column '#0' as it no longer exists")
234
267
 
235
268
  def on_tree_click(self, event: Any) -> None:
236
269
  """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
270
+ column_name = self.column_manager.get_column_from_click_position(event.x)
246
271
 
247
- col_idx = int(column_id_str.replace("#", "")) - 1
248
- if col_idx < 0:
272
+ if column_name is None:
273
+ # Click was on the tree part or outside columns, clear selection
274
+ self.column_manager.clear_selection()
249
275
  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
276
 
256
- if column_name == self.selected_column_id:
277
+ if column_name == self.column_manager.selected_column_id:
278
+ # Already selected, do nothing
257
279
  return
258
280
 
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
281
+ # Set the new selected column
282
+ self.column_manager.set_selected_column(column_name)
270
283
 
271
284
  def double_click_handler(self, event: Any) -> None:
272
285
  current_selection = self.tree.selection()
@@ -324,11 +337,44 @@ class MainView(CTkView):
324
337
  self.edit_event_data = None
325
338
  return result
326
339
 
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()
340
+ def expand_all_items(self) -> None:
341
+ """Expand all items in the tree view."""
342
+
343
+ def expand_recursive(item: str) -> None:
344
+ self.tree.item(item, open=True)
345
+ children = self.tree.get_children(item)
346
+ for child in children:
347
+ expand_recursive(child)
348
+
349
+ # Start with root items
350
+ root_items = self.tree.get_children()
351
+ for item in root_items:
352
+ expand_recursive(item)
353
+
354
+ def collapse_all_items(self) -> None:
355
+ """Collapse all items in the tree view."""
356
+
357
+ def collapse_recursive(item: str) -> None:
358
+ children = self.tree.get_children(item)
359
+ for child in children:
360
+ collapse_recursive(child)
361
+ self.tree.item(item, open=False)
362
+
363
+ # Start with root items
364
+ root_items = self.tree.get_children()
365
+ for item in root_items:
366
+ collapse_recursive(item)
367
+
368
+ def on_tree_control_segment_click(self, value: str) -> None:
369
+ """Handle clicks on the tree control segmented button."""
370
+ if value == "⊞":
371
+ self.expand_all_items()
372
+ elif value == "⊟":
373
+ self.collapse_all_items()
374
+ elif value == "🔄":
375
+ self.trigger_refresh_event()
376
+ # Reset selection to avoid button staying selected
377
+ self.tree_control_segment.set("")
332
378
 
333
379
  def open_column_selection_dialog(self) -> None:
334
380
  """Open a dialog to select which columns to display."""
@@ -341,20 +387,24 @@ class MainView(CTkView):
341
387
  checkbox_frame = customtkinter.CTkFrame(dialog)
342
388
  checkbox_frame.pack(padx=10, pady=10, fill="both", expand=True)
343
389
 
344
- # Create a variable for each column
345
- self.column_vars = {}
346
- for column_name in self.all_columns:
390
+ # Create a variable for each column using ColumnManager
391
+ for column_name in self.column_manager.all_columns:
347
392
  # 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)
393
+ is_visible = column_name in self.column_manager.visible_columns
394
+
395
+ # Get or create variable
396
+ if column_name not in self.column_manager.column_vars:
397
+ self.column_manager.column_vars[column_name] = tkinter.BooleanVar(value=is_visible)
398
+ else:
399
+ self.column_manager.column_vars[column_name].set(is_visible)
400
+
350
401
  checkbox = customtkinter.CTkCheckBox(
351
402
  master=checkbox_frame,
352
403
  text=column_name,
353
404
  command=self.update_visible_columns,
354
- variable=var,
405
+ variable=self.column_manager.column_vars[column_name],
355
406
  )
356
407
  checkbox.pack(anchor="w", padx=5, pady=2)
357
- self.column_vars[column_name] = var
358
408
 
359
409
  # Add OK and Cancel buttons
360
410
  button_frame = customtkinter.CTkFrame(dialog)
@@ -383,11 +433,35 @@ class MainView(CTkView):
383
433
  dialog.transient(self.root) # Keep the dialog above the main window
384
434
  dialog.grab_set() # Make the dialog modal
385
435
 
436
+ def update_visible_columns(self) -> None:
437
+ """Wrapper method to update visible columns via ColumnManager."""
438
+ self.column_manager.update_visible_columns()
439
+
440
+ def update_data(self, elements: list[EditableConfigElement], variants: list[VariantViewData]) -> None:
441
+ """Update the view with refreshed data."""
442
+ self.elements = elements
443
+ self.elements_dict = {elem.name: elem for elem in elements}
444
+ self.variants = variants
445
+
446
+ # Clear the tree first
447
+ for item in self.tree.get_children():
448
+ self.tree.delete(item)
449
+
450
+ # Use ColumnManager to handle all column-related updates
451
+ self.column_manager.update_columns(variants)
452
+
453
+ # Repopulate the tree view
454
+ self.tree_view_items_mapping = self.populate_tree_view()
455
+ self.adjust_column_width()
456
+
457
+ # ...existing code...
458
+
386
459
 
387
460
  class KSPL(Presenter):
388
461
  def __init__(self, event_manager: EventManager, project_dir: Path) -> None:
389
462
  self.event_manager = event_manager
390
463
  self.event_manager.subscribe(KSplEvents.EDIT, self.edit)
464
+ self.event_manager.subscribe(KSplEvents.REFRESH, self.refresh)
391
465
  self.logger = logger.bind()
392
466
  self.kconfig_data = SPLKConfigData(project_dir)
393
467
  self.view = MainView(
@@ -411,6 +485,31 @@ class KSPL(Presenter):
411
485
  raise ValueError(f"Could not find config element '{edit_event_data.config_element_name}'")
412
486
  config_element.value = edit_event_data.new_value
413
487
 
488
+ def refresh(self) -> None:
489
+ """Handle refresh event by reloading data and updating the view."""
490
+ self.logger.info("Refreshing KConfig data...")
491
+ try:
492
+ # Store old state for debugging
493
+ old_variants = [v.name for v in self.kconfig_data.get_variants()]
494
+ self.logger.debug(f"Before refresh: {len(old_variants)} variants: {old_variants}")
495
+
496
+ self.kconfig_data.refresh_data()
497
+
498
+ # Log new state
499
+ new_variants = [v.name for v in self.kconfig_data.get_variants()]
500
+ self.logger.debug(f"After refresh: {len(new_variants)} variants: {new_variants}")
501
+
502
+ # Update the view with new data
503
+ self.view.update_data(
504
+ self.kconfig_data.get_elements(),
505
+ self.kconfig_data.get_variants(),
506
+ )
507
+ self.logger.info("Data refreshed successfully")
508
+ except Exception as e:
509
+ self.logger.error(f"Failed to refresh data: {e}")
510
+ # Don't re-raise the exception to prevent the GUI from crashing
511
+ # Instead, keep the current data state
512
+
414
513
  def run(self) -> None:
415
514
  self.view.mainloop()
416
515
 
@@ -442,3 +541,112 @@ class GuiCommand(Command):
442
541
 
443
542
  def _register_arguments(self, parser: ArgumentParser) -> None:
444
543
  register_arguments_for_config_dataclass(parser, GuiCommandConfig)
544
+
545
+
546
+ class ColumnManager:
547
+ """Manages column state, visibility, selection, and headings for the treeview."""
548
+
549
+ def __init__(self, tree: ttk.Treeview) -> None:
550
+ self.tree = tree
551
+ self.all_columns: list[str] = []
552
+ self.visible_columns: list[str] = []
553
+ self.header_texts: dict[str, str] = {}
554
+ self.selected_column_id: str | None = None
555
+ self.column_vars: dict[str, tkinter.BooleanVar] = {}
556
+
557
+ def update_columns(self, variants: list[VariantViewData]) -> None:
558
+ """Update column configuration with new variants."""
559
+ # Clear any existing selection FIRST, before any tree operations
560
+ self.selected_column_id = None
561
+
562
+ # Update column lists
563
+ new_all_columns = [v.name for v in variants]
564
+
565
+ # Preserve visible columns that still exist, add new ones
566
+ if self.visible_columns:
567
+ existing_visible = [col for col in self.visible_columns if col in new_all_columns]
568
+ new_columns = [col for col in new_all_columns if col not in existing_visible]
569
+ self.visible_columns = existing_visible + new_columns
570
+ else:
571
+ self.visible_columns = list(new_all_columns)
572
+
573
+ # Update all_columns after determining visible columns
574
+ self.all_columns = new_all_columns
575
+
576
+ # Update tree configuration with error handling
577
+ try:
578
+ # First completely clear the tree configuration
579
+ self.tree.configure(columns=(), displaycolumns=())
580
+ # Then set new configuration
581
+ self.tree["columns"] = tuple(self.all_columns)
582
+ self.tree["displaycolumns"] = self.visible_columns
583
+ except tkinter.TclError:
584
+ # If there's still an error, log it but continue
585
+ pass
586
+
587
+ # Update header texts and tree headings
588
+ self.header_texts = {}
589
+ for variant in variants:
590
+ self.header_texts[variant.name] = variant.name
591
+ try:
592
+ self.tree.heading(variant.name, text=variant.name)
593
+ except tkinter.TclError:
594
+ # Column might not exist yet, will be handled in next update
595
+ pass
596
+
597
+ # Clean up column variables for dialog
598
+ self.column_vars = {k: v for k, v in self.column_vars.items() if k in self.all_columns}
599
+
600
+ def set_selected_column(self, column_name: str) -> bool:
601
+ """Set the selected column and update its visual state. Returns True if successful."""
602
+ if column_name not in self.all_columns:
603
+ return False
604
+
605
+ # Clear previous selection
606
+ self._clear_selection()
607
+
608
+ # Set new selection
609
+ self.selected_column_id = column_name
610
+ original_text = self.header_texts.get(column_name)
611
+ if original_text:
612
+ try:
613
+ self.tree.heading(column_name, text=f"✅{original_text}")
614
+ return True
615
+ except tkinter.TclError:
616
+ self.selected_column_id = None
617
+ return False
618
+ return False
619
+
620
+ def clear_selection(self) -> None:
621
+ """Clear the column selection."""
622
+ self._clear_selection()
623
+
624
+ def _clear_selection(self) -> None:
625
+ """Internal method to clear selection without public access."""
626
+ if self.selected_column_id and self.selected_column_id in self.header_texts:
627
+ # Only try to clear if the column still exists in the tree
628
+ if self.selected_column_id in self.all_columns:
629
+ original_text = self.header_texts[self.selected_column_id]
630
+ try:
631
+ self.tree.heading(self.selected_column_id, text=original_text)
632
+ except tkinter.TclError:
633
+ # Column no longer exists in tree, that's fine
634
+ pass
635
+ self.selected_column_id = None
636
+
637
+ def get_column_from_click_position(self, x: int) -> str | None:
638
+ """Get column name from click position, returns None if invalid."""
639
+ column_id_str = self.tree.identify_column(x)
640
+ if not column_id_str or column_id_str == "#0":
641
+ return None
642
+
643
+ col_idx = int(column_id_str.replace("#", "")) - 1
644
+ if col_idx < 0 or col_idx >= len(self.visible_columns):
645
+ return None
646
+
647
+ return self.visible_columns[col_idx]
648
+
649
+ def update_visible_columns(self) -> None:
650
+ """Update visible columns based on column_vars state."""
651
+ self.visible_columns = [col_name for col_name, var in self.column_vars.items() if var.get()]
652
+ 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.3.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=F5mW07pSyGrqDNY2Ehr-UpDzpBtN-FsYU0QGZWf6PJE,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=reOCaxxp5do7F68II3gCu-WkT6fG98_5pVqEeNEHe6U,26397
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.3.0.dist-info/LICENSE,sha256=eJwI5n6lDPIjogNcYBln7NKGtreVlek905oBBY_HnxQ,1066
11
+ kspl-1.3.0.dist-info/METADATA,sha256=6LzdWwKxOyGklRRfzXd1idcv331g-PshBSidInNoG9A,4842
12
+ kspl-1.3.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
13
+ kspl-1.3.0.dist-info/entry_points.txt,sha256=6haBy5AuYGJblw1cWUTx5N41Ilg4YALABhi1psEvruY,39
14
+ kspl-1.3.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