From f7d757cd5dcd66c68bb7ebb3c40f7311ed0ff95d Mon Sep 17 00:00:00 2001
From: Christoph Schmidt <christoph.,schmidt@tugraz.at>
Date: Mon, 27 Nov 2023 12:52:35 +0100
Subject: [PATCH] Updated to version 1.0.0 Added new example. Updated README.md

---
 .idea/confighandler.iml                  |   3 +
 ExampleConfig.py                         |  56 ++++++++
 README.md                                | 175 ++++++++++++++++++++---
 examples/main.py                         |  10 +-
 pyproject.toml                           |   2 +-
 requirements.txt                         |   3 +
 src/confighandler/__init__.py            |  14 ++
 src/confighandler/controller/__init__.py |  11 ++
 src/confighandler/view/__init__.py       |  11 ++
 9 files changed, 255 insertions(+), 30 deletions(-)
 create mode 100644 ExampleConfig.py
 create mode 100644 requirements.txt
 create mode 100644 src/confighandler/controller/__init__.py
 create mode 100644 src/confighandler/view/__init__.py

diff --git a/.idea/confighandler.iml b/.idea/confighandler.iml
index 0f34eda..42e1ed6 100644
--- a/.idea/confighandler.iml
+++ b/.idea/confighandler.iml
@@ -8,6 +8,9 @@
     <orderEntry type="jdk" jdkName="Python 3.12 (confighandler)" jdkType="Python SDK" />
     <orderEntry type="sourceFolder" forTests="false" />
   </component>
+  <component name="PackageRequirementsSettings">
+    <option name="keepMatchingSpecifier" value="false" />
+  </component>
   <component name="PyNamespacePackagesService">
     <option name="namespacePackageFolders">
       <list>
diff --git a/ExampleConfig.py b/ExampleConfig.py
new file mode 100644
index 0000000..35144ab
--- /dev/null
+++ b/ExampleConfig.py
@@ -0,0 +1,56 @@
+# -*- coding: utf-8 -*-
+"""
+Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at>
+Created: 2023-10-19 12:35
+Package Version: 
+Description: 
+"""
+import sys
+
+sys.path.append("../src")
+import confighandler as cfg
+
+
+class SecondConfig(cfg.ConfigNode):
+
+    def __init__(self, enable_log=True) -> None:
+        # Call the base class (important!)
+        super().__init__(enable_log=enable_log)
+
+        # Some fields
+        # Create a field of type int. Set a default value, a friendly name and a description
+        self.test_int: cfg.Field[int] = cfg.Field(1,
+                                                  friendly_name="My Test Int",
+                                                  description="This is just an integer")
+        self.register()
+
+
+class ApplicationConfig(cfg.ConfigNode):
+
+    def __init__(self, enable_log=True) -> None:
+        # Call the base class (important!)
+        super().__init__(enable_log=enable_log)
+
+        # Some fields
+        # Create a field of type int. Set a default value, a friendly name and a description
+        self.counter: cfg.Field[int] = cfg.Field(1,
+                                                 friendly_name="My Counter",
+                                                 description="This is just an integer")
+
+        self.version: cfg.Field[str] = cfg.Field("v1.0",
+                                                 friendly_name="Version",
+                                                 description="The version")
+
+        # You can also omit the friendly name and description
+        self.check: cfg.Field[bool] = cfg.Field(False)
+
+        # Some other fields
+        # Also possible to create a field of type list
+        self.my_tuple: cfg.Field[tuple] = cfg.Field((1, 2))
+        self.any_list: cfg.Field[list] = cfg.Field([1, 2])
+
+        # Even a nested config is possible
+        self.second_config: SecondConfig = SecondConfig()
+
+        # Don't forget to register the fields (important!)
+        self.register()
diff --git a/README.md b/README.md
index b622602..56e84e1 100644
--- a/README.md
+++ b/README.md
@@ -1,39 +1,172 @@
 # ConfigHandler
 
 ## Description
-This module is used to handle configuration files for flexsensor.
-It hgas been designed, so the user can create a config with an UI, automatically connected to the signal and slots.
+This module is used to handle configuration files for FlexSensor.
+It has been designed, so the user can create a config with a UI, automatically connected to the signal and slots.
 
 ## Requirements
 Install the requirements with pip:
-```python
-pip install pyside6, rich, pyyaml
+```bash
+pip install -r requirements.txt
 ````
+or
+```bash
+pip install PySide6 PyYAML rich
+```
+## Installing this repo to yout project using pip
+Just add the following line to your requirements.txt
+```bash
+git+https://gitlab.tugraz.at/flexsensor-public/lasercontrol.git@<branch>
+# e.g., from branch main
+git+https://gitlab.tugraz.at/flexsensor-public/lasercontrol.git@main
+```
+oder directly using pip (without requirements.txt)
+```bash
+# or manually
+pip install git+https://gitlab.tugraz.at/flexsensor-public/lasercontrol.git@<branch>
+```
 ## Usage
-The Usage is straight forward. Just create a new ```ConfigNode``` object and call the show() method.
+Example files can be found in `./examples`.
+The usage is straight forward. Just create a new ```ConfigNode``` object and call the show() method.
+### Creating a config
+Before you can start working woith the config, you need to create some kind of sceleton. This
+has three advantages:
+1. You can define the type of the fields. You can give them friendly names and descriptions.
+2. The parsing of the config file is much easier, because the parser knows the type of the fields.
+3. When working with this library, you can use the auto completion of your IDE, since it is a class.
 ```python
+import confighandler as cfg
 
-class ApplicationConfig(ConfigNode):
-
-    def __init__(self) -> None:
+class SecondConfig(cfg.ConfigNode):
+    
+    def __init__(self, enable_log=True) -> None:
         # Call the base class (important!)
-        super().__init__()
-
+        super().__init__(enable_log=enable_log)
+        
         # Some fields
-        self.output_directory: Field[Path] = Field(Path("C:\\{wafer_nr}"))
-        self.wafer_version: Field[str] = Field("v1.0")
-        self.wafer_number: Field[int] = Field(1)
-        # A field with an description
-        self.wafer_nr: Field[str] = Field("12345ABCD_{wafer_number}",
-                                          friendly_name="Wafer Number",
-                                          description="The version of the wafer")
+        # Create a field of type int. Set a default value, a friendly name and a description
+        self.test_int: cfg.Field[int] = cfg.Field(1,
+                                              friendly_name="My Test Int",
+                                              description="This is just an integer")  
+#
+class ApplicationConfig(cfg.ConfigNode):
+    
+    def __init__(self, enable_log=True) -> None:
+        # Call the base class (important!)
+        super().__init__(enable_log=enable_log)
+        
+        # Some fields
+        # Create a field of type int. Set a default value, a friendly name and a description
+        self.counter: cfg.Field[int] = cfg.Field(1,
+                                                 friendly_name="My Counter",
+                                                 description="This is just an integer")
+
+        
+        self.version: cfg.Field[str] = cfg.Field("v1.0",
+                                                 friendly_name="Version",
+                                                 description="The version")
+        
+        # You can also omit the friendly name and description
+        self.check: cfg.Field[bool] = cfg.Field(False)
+        
+
         # Some other fields
-        self.my_tuple: Field[tuple] = Field((1,2))
-        self.any_list: Field[list] = Field([1, 2])
+        # Also possible to create a field of type list
+        self.my_tuple: cfg.Field[tuple] = cfg.Field((1,2))
+        self.any_list: cfg.Field[list] = cfg.Field([1, 2])
         
         # Even a nested config is possible
-        self.other_config: OtherConfig = OtherConfig()
+        self.second_config: SecondConfig = SecondConfig()
 
         # Don't forget to register the fields (important!)
         self.register()
-```
\ No newline at end of file
+```
+
+### Loading and saving a config
+It is possible to load and save a config. The config is saved as a yaml file.
+```python
+from ApplicationConfig import ApplicationConfig
+
+if __name__ == "__main__":
+    config = ApplicationConfig(enable_log=True)
+    config.save('./configs/ApplicationConfig.yaml')
+    config.load('./configs/ApplicationConfig.yaml')
+```
+
+### Autosaving
+It is also possible to autosave a config. This is useful, if you want to save the config, when the user changes a value.
+```python
+from ApplicationConfig import ApplicationConfig
+
+if __name__ == "__main__":
+    config = ApplicationConfig(enable_log=True)
+    config.autosave(enable=True, path='./configs_autosave')
+```
+
+### Creating a UI
+The condifg handler build an UI in the background. You can access the widget using
+```python
+import logging
+import sys
+
+from PySide6 import QtWidgets
+from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QTreeWidget
+
+from ApplicationConfig import ApplicationConfig
+
+if __name__ == "__main__":
+    # Creating the UI
+    window = QMainWindow()
+    wdg = QWidget()
+    grd = QtWidgets.QGridLayout()
+    wdg.setLayout(grd)
+
+    # Add the config to the UI
+    config = ApplicationConfig(enable_log=True)
+    conf_view = config.view.widget()
+    grd.addWidget(conf_view, 0, 0)
+
+    window.setCentralWidget(wdg)
+    window.show()
+    sys.exit(app.exec())
+```
+or using a tree view (which is sometimes more useful)
+```python
+import logging
+import sys
+
+from PySide6 import QtWidgets
+from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QTreeWidget
+
+from ApplicationConfig import ApplicationConfig
+
+if __name__ == "__main__":
+    # Creating the UI
+    window = QMainWindow()
+    wdg = QWidget()
+    grd = QtWidgets.QGridLayout()
+    wdg.setLayout(grd)
+
+    # Add the config to the UI
+    config = ApplicationConfig(enable_log=True)
+    # Create a tree view
+    tree = QTreeWidget()
+    tree.setColumnCount(3)
+    tree.setHeaderLabels(["Name", "Type", "Description"])
+    
+    # Get the tree item
+    config_item = config.view.ui_tree_widget_item(tree)
+    
+    tree.addTopLevelItem(config_item)
+    grd.addWidget(tree, 2, 0)
+    
+    window.setCentralWidget(wdg)
+    window.show()
+    sys.exit(app.exec())
+```
+## Troubleshoting
+When working insde the exmaples folder, you need to add the confighandler folder to the python path.
+```python
+import sys
+sys.path.append('../src/')
+```
diff --git a/examples/main.py b/examples/main.py
index 4eac1a3..5eb5459 100644
--- a/examples/main.py
+++ b/examples/main.py
@@ -11,16 +11,10 @@ if __name__ == "__main__":
     app = QApplication(sys.argv)
 
     # setup the logging module
-    FORMAT = "%(message)s"
-    logging.basicConfig(
-        level="DEBUG", format=FORMAT, datefmt="[%X]", handlers=[
-            RichHandler(rich_tracebacks=True)
-        ]
-    )
 
     config = ApplicationConfig(enable_log=True)
-    print(config.load('./configs/ApplicationConfig.yaml'))
-    config.autosave(enable=True, path='./configs_autosave')
+    #print(config.load('./configs/ApplicationConfig.yaml'))
+    #config.autosave(enable=True, path='./configs_autosave')
 
     #print(config.wafer_version)
     #config.wafer_version.get()
diff --git a/pyproject.toml b/pyproject.toml
index 6338df0..9791031 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
 [project]
 name = "confighandler"
-version = "0.0.3"
+version = "1.0.0"
 authors = [
   { name="Christoph Schmidt", email="cschmidt.fs@gmail.com" },
 ]
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..23b0de5
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+PySide6
+PyYAML
+rich
\ No newline at end of file
diff --git a/src/confighandler/__init__.py b/src/confighandler/__init__.py
index bfa7857..89aeb3f 100644
--- a/src/confighandler/__init__.py
+++ b/src/confighandler/__init__.py
@@ -1,2 +1,16 @@
+import logging
+import os
+import sys
+
+from rich.logging import RichHandler
+
+FORMAT = "%(message)s"
+logging.basicConfig(
+    level="DEBUG", format=FORMAT, datefmt="[%X]", handlers=[
+        RichHandler(rich_tracebacks=True)
+    ]
+)
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '../'))
 from .controller.Field import Field
 from .controller.ConfigNode import ConfigNode
\ No newline at end of file
diff --git a/src/confighandler/controller/__init__.py b/src/confighandler/controller/__init__.py
new file mode 100644
index 0000000..62c0acc
--- /dev/null
+++ b/src/confighandler/controller/__init__.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+"""
+Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at>
+Created: 2023-10-19 12:35
+Package Version: 
+Description: 
+"""
+import os
+import sys
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '../'))
\ No newline at end of file
diff --git a/src/confighandler/view/__init__.py b/src/confighandler/view/__init__.py
new file mode 100644
index 0000000..62c0acc
--- /dev/null
+++ b/src/confighandler/view/__init__.py
@@ -0,0 +1,11 @@
+# -*- coding: utf-8 -*-
+"""
+Author(s): Christoph Schmidt <christoph.schmidt@tugraz.at>
+Created: 2023-10-19 12:35
+Package Version: 
+Description: 
+"""
+import os
+import sys
+
+sys.path.append(os.path.join(os.path.dirname(__file__), '../'))
\ No newline at end of file
-- 
GitLab