Bläddra i källkod

split VulcanBoard.py into modules + add gitignore

Noah Vogt 6 månader sedan
förälder
incheckning
6a45615d11
11 ändrade filer med 282 tillägg och 145 borttagningar
  1. 1 0
      .gitignore
  2. 3 145
      VulcanBoard.py
  3. 18 0
      config/__init__.py
  4. 25 0
      config/classes.py
  5. 119 0
      config/load.py
  6. 23 0
      config/path.py
  7. 27 0
      config/validate.py
  8. 18 0
      util/__init__.py
  9. 18 0
      util/exception.py
  10. 10 0
      util/gui.py
  11. 20 0
      util/log.py

+ 1 - 0
.gitignore

@@ -0,0 +1 @@
+*/__pycache__/*

+ 3 - 145
VulcanBoard.py

@@ -16,167 +16,26 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 
 # pylint: disable=invalid-name
 # pylint: disable=invalid-name
-from os import path, getenv, name
 import subprocess
 import subprocess
 import sys
 import sys
-from dataclasses import dataclass
 
 
-import yaml
-from termcolor import colored
 import colorama
 import colorama
 
 
 from kivy.app import App
 from kivy.app import App
 from kivy.uix.button import Button
 from kivy.uix.button import Button
 from kivy.uix.gridlayout import GridLayout
 from kivy.uix.gridlayout import GridLayout
 from kivy.utils import get_color_from_hex
 from kivy.utils import get_color_from_hex
-from kivy.factory import Factory
 
 
-
-def log(message: str, color="green") -> None:
-    print(colored("[*] {}".format(message), color))  # pyright: ignore
-
-
-def get_config_path():
-    if name == "nt":
-        return path.join(getenv("APPDATA", ""), "VulcanBoard", "config.yml")
-    xdg_config_home = getenv("XDG_CONFIG_HOME", path.expanduser("~/.config"))
-    return path.join(xdg_config_home, "VulcanBoard", "config.yml")
-
-
-VALID_HEX_COLORS = list("0123456789abcdef")
-
-
-def is_valid_hexcolor(hexcolor: str) -> bool:
-    if len(hexcolor) != 6:
-        return False
-
-    for char in hexcolor.lower():
-        if char not in VALID_HEX_COLORS:
-            return False
-
-    return True
-
-
-@dataclass
-class Config:
-    columns: int
-    rows: int
-    buttons: list[dict]
-    spacing: int
-    padding: int
-
-
-class CustomException(Exception):
-    pass
-
-
-@dataclass
-class ConfigLoader:
-    config_path: str
-
-    def __post_init__(self) -> None:
-        self.columns = 0
-        self.rows = 0
-        self.buttons = []
-        self.padding = 0
-        self.spacing = 0
-
-    def get_config(self) -> Config | str:
-        try:
-            with open(self.config_path, "r", encoding="utf-8") as config_reader:
-                yaml_config = yaml.safe_load(config_reader)
-                self.columns = yaml_config.get("columns")
-                self.rows = yaml_config.get("rows")
-                self.buttons = yaml_config.get("buttons")
-                self.padding = yaml_config.get("padding", 5)
-                self.spacing = yaml_config.get("spacing", 5)
-                return self.__interpret_config()
-        except (FileNotFoundError, PermissionError, IOError) as error:
-            return f"Error: Could not access config file at {self.config_path}. Reason: {error}"
-        except (yaml.YAMLError, CustomException) as error:
-            return f"Error parsing config file. Reason: {error}"
-
-    def __interpret_config(self) -> Config:
-        self.__validate_dimensions()
-        self.__validate_buttons()
-        self.__validate_styling()
-
-        return Config(
-            self.columns, self.rows, self.buttons, self.spacing, self.padding
-        )
-
-    def __validate_buttons(self) -> None:
-        if not isinstance(self.buttons, list):
-            raise CustomException(
-                "invalid button config. needs to be a list of dicts."
-            )
-        for button in self.buttons:
-            if not isinstance(button, dict):
-                raise CustomException(
-                    "invalid button config. needs to be a list of dicts."
-                )
-
-            if (
-                not isinstance(dimensions := button.get("position", ""), list)
-                or (not isinstance(dimensions[0], int))
-                or (not isinstance(dimensions[1], int))
-                or (0 > dimensions[0] or dimensions[0] > self.rows - 1)
-                or (0 > dimensions[1] or dimensions[1] > self.columns - 1)
-            ):
-                raise CustomException(
-                    f"invalid button 'position' subentry: '{dimensions}'"
-                )
-
-            for entry in ("txt", "cmd"):
-                if not isinstance(result := button.get(entry, ""), str):
-                    raise CustomException(
-                        f"invalid button '{entry}' subentry: '{result}'"
-                    )
-
-            if not isinstance(
-                bg_color := button.get("bg_color", "cccccc"), str
-            ) or not is_valid_hexcolor(bg_color):
-                raise CustomException(
-                    f"invalid button 'bg_color' subentry: '{bg_color}'"
-                )
-
-            if not isinstance(
-                fg_color := button.get("fg_color", "ffffff"), str
-            ) or not is_valid_hexcolor(bg_color):
-                raise CustomException(
-                    f"invalid button 'fg_color' subentry: '{fg_color}'"
-                )
-
-            if (
-                not isinstance(fontsize := button.get("fontsize", ""), int)
-                or 0 > fontsize
-            ):
-                raise CustomException(
-                    f"invalid button 'fontsize' subentry: '{fontsize}'"
-                )
-
-    def __validate_dimensions(self) -> None:
-        for dimension in (self.columns, self.rows):
-            if not isinstance(dimension, int) or (dimension <= 0):
-                raise CustomException(f"invalid dimension: {dimension}")
-
-    def __validate_styling(self) -> None:
-        for styling in (self.spacing, self.padding):
-            if not isinstance(styling, int) or (styling <= 0):
-                raise CustomException(f"invalid styling: {styling}")
+from util import log, error_exit_gui
+from config import get_config_path, ConfigLoader, Config
 
 
 
 
 class VulcanBoardApp(App):
 class VulcanBoardApp(App):
     def build(self):
     def build(self):
         config_loader = ConfigLoader(get_config_path())
         config_loader = ConfigLoader(get_config_path())
         config = config_loader.get_config()  # pyright: ignore
         config = config_loader.get_config()  # pyright: ignore
-        print(type(config))
-        print(config)
         if isinstance(config, str):
         if isinstance(config, str):
-            popup = Factory.ErrorPopup()
-            popup.message.text = config
-            popup.open()
-            popup.error_exit = lambda: sys.exit(1)
+            error_exit_gui(config)
         else:
         else:
             config: Config = config
             config: Config = config
             button_map = {
             button_map = {
@@ -226,7 +85,6 @@ class VulcanBoardApp(App):
             return layout
             return layout
 
 
     def config_error_exit(self, popup):
     def config_error_exit(self, popup):
-        print("wow")
         popup.dismiss()
         popup.dismiss()
         sys.exit(1)
         sys.exit(1)
 
 

+ 18 - 0
config/__init__.py

@@ -0,0 +1,18 @@
+# Copyright © 2024 Noah Vogt <noah@noahvogt.com>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from .path import get_config_path
+from .classes import Config
+from .load import ConfigLoader

+ 25 - 0
config/classes.py

@@ -0,0 +1,25 @@
+# Copyright © 2024 Noah Vogt <noah@noahvogt.com>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from dataclasses import dataclass
+
+
+@dataclass
+class Config:
+    columns: int
+    rows: int
+    buttons: list[dict]
+    spacing: int
+    padding: int

+ 119 - 0
config/load.py

@@ -0,0 +1,119 @@
+# Copyright © 2024 Noah Vogt <noah@noahvogt.com>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from dataclasses import dataclass
+
+import yaml
+
+from util import CustomException
+
+from .classes import Config
+from .validate import is_valid_hexcolor
+
+
+@dataclass
+class ConfigLoader:
+    config_path: str
+
+    def __post_init__(self) -> None:
+        self.columns = 0
+        self.rows = 0
+        self.buttons = []
+        self.padding = 0
+        self.spacing = 0
+
+    def get_config(self) -> Config | str:
+        try:
+            with open(self.config_path, "r", encoding="utf-8") as config_reader:
+                yaml_config = yaml.safe_load(config_reader)
+                self.columns = yaml_config.get("columns")
+                self.rows = yaml_config.get("rows")
+                self.buttons = yaml_config.get("buttons")
+                self.padding = yaml_config.get("padding", 5)
+                self.spacing = yaml_config.get("spacing", 5)
+                return self.__interpret_config()
+        except (FileNotFoundError, PermissionError, IOError) as error:
+            return f"Error: Could not access config file at {self.config_path}. Reason: {error}"
+        except (yaml.YAMLError, CustomException) as error:
+            return f"Error parsing config file. Reason: {error}"
+
+    def __interpret_config(self) -> Config:
+        self.__validate_dimensions()
+        self.__validate_buttons()
+        self.__validate_styling()
+
+        return Config(
+            self.columns, self.rows, self.buttons, self.spacing, self.padding
+        )
+
+    def __validate_buttons(self) -> None:
+        if not isinstance(self.buttons, list):
+            raise CustomException(
+                "invalid button config. needs to be a list of dicts."
+            )
+        for button in self.buttons:
+            if not isinstance(button, dict):
+                raise CustomException(
+                    "invalid button config. needs to be a list of dicts."
+                )
+
+            if (
+                not isinstance(dimensions := button.get("position", ""), list)
+                or (not isinstance(dimensions[0], int))
+                or (not isinstance(dimensions[1], int))
+                or (0 > dimensions[0] or dimensions[0] > self.rows - 1)
+                or (0 > dimensions[1] or dimensions[1] > self.columns - 1)
+            ):
+                raise CustomException(
+                    f"invalid button 'position' subentry: '{dimensions}'"
+                )
+
+            for entry in ("txt", "cmd"):
+                if not isinstance(result := button.get(entry, ""), str):
+                    raise CustomException(
+                        f"invalid button '{entry}' subentry: '{result}'"
+                    )
+
+            if not isinstance(
+                bg_color := button.get("bg_color", "cccccc"), str
+            ) or not is_valid_hexcolor(bg_color):
+                raise CustomException(
+                    f"invalid button 'bg_color' subentry: '{bg_color}'"
+                )
+
+            if not isinstance(
+                fg_color := button.get("fg_color", "ffffff"), str
+            ) or not is_valid_hexcolor(bg_color):
+                raise CustomException(
+                    f"invalid button 'fg_color' subentry: '{fg_color}'"
+                )
+
+            if (
+                not isinstance(fontsize := button.get("fontsize", ""), int)
+                or 0 > fontsize
+            ):
+                raise CustomException(
+                    f"invalid button 'fontsize' subentry: '{fontsize}'"
+                )
+
+    def __validate_dimensions(self) -> None:
+        for dimension in (self.columns, self.rows):
+            if not isinstance(dimension, int) or (dimension <= 0):
+                raise CustomException(f"invalid dimension: {dimension}")
+
+    def __validate_styling(self) -> None:
+        for styling in (self.spacing, self.padding):
+            if not isinstance(styling, int) or (styling <= 0):
+                raise CustomException(f"invalid styling: {styling}")

+ 23 - 0
config/path.py

@@ -0,0 +1,23 @@
+# Copyright © 2024 Noah Vogt <noah@noahvogt.com>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from os import path, getenv, name
+
+
+def get_config_path():
+    if name == "nt":
+        return path.join(getenv("APPDATA", ""), "VulcanBoard", "config.yml")
+    xdg_config_home = getenv("XDG_CONFIG_HOME", path.expanduser("~/.config"))
+    return path.join(xdg_config_home, "VulcanBoard", "config.yml")

+ 27 - 0
config/validate.py

@@ -0,0 +1,27 @@
+# Copyright © 2024 Noah Vogt <noah@noahvogt.com>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+VALID_HEX_COLORS = list("0123456789abcdef")
+
+
+def is_valid_hexcolor(hexcolor: str) -> bool:
+    if len(hexcolor) != 6:
+        return False
+
+    for char in hexcolor.lower():
+        if char not in VALID_HEX_COLORS:
+            return False
+
+    return True

+ 18 - 0
util/__init__.py

@@ -0,0 +1,18 @@
+# Copyright © 2024 Noah Vogt <noah@noahvogt.com>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from .log import log
+from .exception import CustomException
+from .gui import error_exit_gui

+ 18 - 0
util/exception.py

@@ -0,0 +1,18 @@
+# Copyright © 2024 Noah Vogt <noah@noahvogt.com>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+
+class CustomException(Exception):
+    pass

+ 10 - 0
util/gui.py

@@ -0,0 +1,10 @@
+import sys
+
+from kivy.factory import Factory
+
+
+def error_exit_gui(config) -> None:
+    popup = Factory.ErrorPopup()
+    popup.message.text = config
+    popup.open()
+    popup.error_exit = lambda: sys.exit(1)

+ 20 - 0
util/log.py

@@ -0,0 +1,20 @@
+# Copyright © 2024 Noah Vogt <noah@noahvogt.com>
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+from termcolor import colored
+
+
+def log(message: str, color="green") -> None:
+    print(colored("[*] {}".format(message), color))  # pyright: ignore