Просмотр исходного кода

split VulcanBoard.py into modules + add gitignore

Noah Vogt 6 месяцев назад
Родитель
Сommit
6a45615d11
11 измененных файлов с 282 добавлено и 145 удалено
  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/>.
 
 # pylint: disable=invalid-name
-from os import path, getenv, name
 import subprocess
 import sys
-from dataclasses import dataclass
 
-import yaml
-from termcolor import colored
 import colorama
 
 from kivy.app import App
 from kivy.uix.button import Button
 from kivy.uix.gridlayout import GridLayout
 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):
     def build(self):
         config_loader = ConfigLoader(get_config_path())
         config = config_loader.get_config()  # pyright: ignore
-        print(type(config))
-        print(config)
         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:
             config: Config = config
             button_map = {
@@ -226,7 +85,6 @@ class VulcanBoardApp(App):
             return layout
 
     def config_error_exit(self, popup):
-        print("wow")
         popup.dismiss()
         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