Эх сурвалжийг харах

Key and key builder is working well.

Cixo Develop 8 сар өмнө
parent
commit
e1b0e03c72

+ 2 - 4
src/CxPini/__init__.py

@@ -1,5 +1,3 @@
-from .exception import pini_exception
-from .exception import pini_type_exception
+from .exception import pini_syntax
 from .key import key
-from .section import section
-from .pini import pini
+from .key_builder import key_builder

+ 1 - 7
src/CxPini/exception.py

@@ -1,8 +1,2 @@
-class pini_exception(Exception):
-    pass
-
-class pini_type_exception(TypeError):
-    pass
-
-class pini_syntax(SyntaxError):
+class pini_syntax(Exception):
     pass

+ 47 - 184
src/CxPini/key.py

@@ -1,205 +1,68 @@
-from .exception import pini_exception
-from .exception import pini_type_exception
 from .exception import pini_syntax
-from .line import line
 
-class key(line):
-    def __init__(self):
-        super().__init__()
-        self.__value: str | int | float | None = None
+class key:
+    def __init__(
+        self,
+        name: str,
+        content: str | int | float | bool | None = None,
+        comment: str | None = None
+    ):
+        self.name = name
+        self.content = content
+        self.comment = comment
 
     @property
-    def value(self) -> str | int | float | None:
-        return self.__value
+    def name(self) -> str:
+        return self.__name
 
     @property
-    def value_type(self) -> str:
-        if self.__value is None:
-            return "None"
-
-        return type(self.__value).__name__
-
-    @value.setter
-    def value(self, target: str | int | float | None) -> None:
-        if type(target) is str:
-            self.__value = target.strip()
-            return
-    
-        if type(target) is int or type(target) is float or target is None:
-            self.__value = target
-            return
-
-        raise TypeError("Value must be str, int, float or None.")
-
-    def get(self, empty: str | int | float | None) -> str | int | float | None:
-        checked = type(empty) is str
-        checked = checked or type(empty) is int
-        checked = checked or type(empty) is float
-        checked = checked or empty is None
-
-        if not checked:
-            raise TypeError("Default value for empty key type is not valid.")
-
-        if self.__value is None:
-            return empty
-
-        if type(empty) is not type(self.__value):
-            error = "Type of the " + self.name + " is not valid. "
-            error = error + "It must be in " + type(empty).__name__ + ". "
-            error = error + "It is " + type(self.__value).__name__ + "."
-
-            raise pini_type_exception(error)
-
-        return self.__value
-
-    def render(self) -> str:
-        comment = ""
-        
-        if self.comment is not None:
-            comment = "# " + self.comment
-
-        if self.name is None:
-            return comment
-
-        content = self.name + " = "
-
-        if self.__value is not None:
-            content = content + str(self.__value)
-
-        if self.__value is None:
-            content = "### " + content
-        
-        if len(comment) > 0:
-            content = content + (" " * (4 - (len(content) % 4)))
-            content = content + comment
-
-        return content
-
-    def clone(self):
-        copy = key()
-        copy.name = self.name
-        copy.value = self.value
-        copy.comment = self.comment
-
-        return copy
-
-class parse_key():
-    def __new__(cls, line: str) -> key:
-        target = key()
-
-        if type(line) is not str:
-            raise TypeError("Line must be in str type.")
-
-        cls.__init__(target, cls, line)
-
-        return target
+    def content(self) -> str | int | float | bool | None:
+        return self.__content
 
-    def __init__(self, cls, line: str) -> None:
-        line = line.lstrip()
-
-        if len(line) == 0:
-            return
-
-        if line[0:3] == "###":
-            cls.__parse_blank(self, cls, line[3:])
-            return
-
-        cls.__parse_normal(self, cls, line)
-
-    def __split_comment(line: str) -> [str | None, str | None]:
-        line = line.lstrip()
-
-        if len(line) == 0:
-            return None, None
-
-        comment = None
-        content = line.rsplit()
-        comment_position = line.find("#")
-
-        if comment_position != -1:
-            comment = line[comment_position + 1:]
-
-            if comment[0] == " ":
-                comment = comment[1:]
-
-            if len(comment) == 0:
-                comment = None
-
-            content = line[:comment_position].rsplit()
-
-        if len(content) == 0:
-            content = None
-
-        return content, comment
-
-    def __parse_blank(self, cls, line: str) -> None:
-        content, comment = cls.__split_comment(line)
-
-        where = content.find("=")
-
-        if where != -1:
-            content = content[:where].rsplit()
-
-        self.name = content
-        self.value = None
-        self.comment = comment
-
-    def __split_comment(cls, line: str) -> [str | None, str | None]:
-        name = line
-        value = None
-        value_position = line.find("=")
+    @property
+    def comment(self) -> str | None:
+        return self.__comment
 
-        if value_position == -1:
-            return name, value
+    @name.setter
+    def name(self, target: str) -> None:
+        target = target.strip()
         
-        name = line[:value_position].rsplit()
-        value = line[value_position + 1:].lstrip()
-        value = cls.__parse_value(cls, value)
-
-        return name, value
+        if len(target) == 0:
+            raise pini_syntax("Key name lenght could not be blank.")
 
-    def __parse_value(cls, line: str) -> str | None:
-        line = line.strip()
+        self.__name = target
 
-        if len(line) == 0:
-            return None
+    @content.setter
+    def content(self, target: str | int | float | bool | None) -> None:
+        self.__content = target
 
-        if cls.is_number(line):
-            as_float = float(line)
-            as_integer = int(line)
+    @comment.setter
+    def comment(self, target: str) -> None:
+        self.__comment = target
 
-            if as_float == as_integer:
-                return as_integer
+    def __render_content(self) -> str:
+        if type(self.content) is int or type(self.content) is float:
+            return str(self.content)
 
-            return as_float
+        if type(self.content) is str:
+            return "\"" + self.content + "\""
 
-        if line[0] == "'" or line[0] == "\"":
-            start = line[0]
+        if type(self.content) is bool:
+            return "True" if self.content else "False"
 
-            if line[-1] != start:
-                raise pini_syntax("String without stop.")
-
-            line = line[1:-1]
-            where = 0
-
-            while True:
-                where = line.find(start, where)
-
-                if where == -1:
-                    break
+        if self.content is None:
+            return "None"
 
-                if line[where - 1] != "\\":
-                    raise pini_syntax("String stopper not expected.")
+        raise TypeError("Key content must be int, float, string or None.")
 
-        return line 
+    def render(self) -> str:
+        result = self.name + " = "
+        result = result + self.__render_content()
 
-    def is_number(value: str) -> bool:
-        if value[0] == "-":
-            value = value[1:]
+        if self.comment is not None:
+            result = result + " # " + self.__comment
 
-        value = value.replace(".", "")
-        return value.isnumeric()
+        return result
 
-    def __parse_normal(self, cls, line: str) -> None:
-        content, comment = cls.__split_comment(line)
-        name, value = cls.__split_content(cls, content) 
+    def __str__(self) -> str:
+        return self.render()

+ 78 - 0
src/CxPini/key_builder.py

@@ -0,0 +1,78 @@
+from .key import key
+from .exception import pini_syntax
+
+class key_builder:
+    def __new__(cls, line: str) -> key | None:
+        line = line.strip()
+        name = cls.__get_name(cls, line)
+
+        if name is None:
+            return None
+
+        content = cls.__get_content(cls, line)
+        comment = cls.__get_comment(cls, line)
+
+        return key(name, content, comment)
+
+    def __get_comment(cls, line: str) -> str | None:
+        split_position = line.find("#")
+
+        if split_position == -1:
+            return None
+
+        if len(line) == split_position + 1:
+            return None
+
+        return line[split_position + 1:].strip()
+
+    def __get_content(cls, line: str) -> str | int | float | bool | None:
+        split_position = line.find("=")
+        content = line[split_position + 1:].lstrip()
+        comment_position = content.find("#")
+
+        if comment_position != -1:
+            content = content[:comment_position].rstrip()
+
+        return cls.__parse_content(cls, content)
+
+    def __parse_content(cls, target: str) -> str | int | float | bool | None:
+        low = target.lower()
+
+        if low == "true":
+            return True
+
+        if low == "false":
+            return False
+
+        if low == "none":
+            return None
+
+        float_check = target.replace(".", "")
+        
+        if float_check.isnumeric() and not target.isnumeric():
+            if target.count(".") != 1:
+                raise TypeError("Bad float numer, with more than one dot.")
+
+            return float(target)
+
+        if target.isnumeric():
+            return int(target)
+
+        start = target[0]
+
+        if start != "\"" and start != "'":
+            return target
+
+        if target[-1] != start:
+            raise pini_syntax("String content has no end.")
+
+        return target[1:-1]
+
+    def __get_name(cls, line: str) -> str | None:
+        split_position = line.find("=")
+
+        if split_position == -1:
+            return None
+
+        return line[:split_position].rstrip()
+

+ 0 - 56
src/CxPini/line.py

@@ -1,56 +0,0 @@
-class line:
-    def __init__(self):
-        self.__name: str | None = None
-        self.__comment: str | None = None
-
-    @property
-    def name(self) -> str | None:
-        return self.__name
-
-    @name.setter
-    def name(self, target: str | None) -> None:
-        if type(target) is str:
-            self.__name = target
-            return
-
-        if target is None:
-            self.__name = None
-            return
-
-        raise TypeError("New name for the line must be str or None.")
-
-    @property
-    def comment(self) -> str | None:
-        return self.__comment
-
-    @comment.setter
-    def comment(self, target: str | None) -> None:
-        if type(target) is str:
-            self.__comment = target
-            return
-
-        if target is None:
-            self.__comment = None
-            return
-
-        raise TypeError("New comment must be str or None.")
-
-    def add_comment(self, target: str) -> str:
-        if type(target) is not str:
-            raise TypeError("New content of the comment must be str.")
-
-        if type(self.__comment) is not str:
-            self.__comment = ""
-
-        self.__comment = self.__comment + target
-
-        return self.__comment
-
-    def clean_comment(self) -> None:
-        self.__comment = None
-
-    def render(self) -> str:
-        raise Exception("It is implemented in the key or section class.")
-
-    def __str__(self) -> str:
-        return self.render()

+ 0 - 159
src/CxPini/parser.py

@@ -1,159 +0,0 @@
-import pathlib
-import _io
-
-from .pini_exception import pini_exception
-from .pini_exception import pini_syntax
-from .pini import pini
-from .key import key
-from .section import section
-
-class parser():
-    def __init__(self, target: str | pathlib.Path):
-        if target isinstance str:
-            target = pathlib.Path(target)
-
-        if not target.is_file():
-            raise pini_exception("File " + str(target) + " not exists.")
-
-        self.__current = None
-        self.__path = target
-        self.__config = pini()
-
-        self.__process()
-
-    def __get_section(self) -> section:
-        if self.__current is None:
-            self.__current = section() 
-            return self.__get_section()
-
-        return self.__current
-
-    def __new_section(self, name: str) -> section:
-        if type(name) is not str:
-            raise TypeError("New section name must be str.")
-
-        name = name.strip()
-
-        if len(name) == 0:
-            raise pini_syntax("New section name can not be empty.")
-
-        if self.__current is not None:
-            self.__config.add(self.__current)
-
-        self.__current = section()
-        self.__current.name = name
-
-    def __process(self) -> None:
-        with self.__path.open() as file:
-            for line in file:
-                self.__parse_line(line)
-
-    def __parse_line(self, line: str) -> None:
-        line = line.lstrip()
-
-        if len(line) == 0:
-            return
-
-        first = line[0]
-
-        if first == "[":
-            self.__parse_section(line)
-            return
-
-        if first == "#":
-            if len(line) >= 3 and line[0:3] == "###":
-                self.__parse_unactive_key(line)
-                return
-
-            self.__parse_comment(line)
-            return
-
-        self.__parse_key(line)
-
-    def __split_comment(self, line: str) -> [str | None , str | None]:
-        position = line.find("#")
-
-        if position == -1:
-            return line.strip(), None
-
-        content = line[:position].strip()
-        comment = line[position + 1:]
-
-        if len(comment) > 0 and comment[0] == " ":
-            comment = comment[1:]
-        
-        if len(content) == 0:
-            content = None
-
-        if len(comment) == 0:
-            comment = None
-
-        return content, comment
-
-    def __parse_unactive_key(self, line: str) -> None:
-        line = line[3:]
-
-        if line[0] == " ":
-            line = line[1:]
-
-        content, comment = self.__split_comment(line)
-
-        if content is None:
-            if comment is None:
-                return
-
-            self.__get_section().add_comment(comment)
-
-        position = content.find("=")
-
-        target = key()
-        target.value = None
-        target.name = content.strip()
-        target.comment = comment
-
-    def __parse_key(self, line: str) -> None:
-        content, comment = self.__split_comment(line)
-
-        if content is None and comment is not None:
-            self.__parse_comment("#" + comment)
-            return
-
-        if content.count("=") != 1:
-            raise pini_syntax(""
-
-    def __parse_section(self, line: str) -> None:
-        content, comment = self.__split_comment(line)
-
-        if content.count("[") != 1 or content.count("]") != 1:
-            raise pini_syntax("Section name must start with [ and end with ].")
-
-        start = content.find("[") + 1
-        end = content.find("]")
-        name = content[start:end].strip()
-
-        if len(name) == 0:
-            raise pini_syntax("Section name can not be empty.")
-
-        if name.count(" ") > 0 or name.count("\t") > 0:
-            raise pini_syntax("Section name can not contain white chars.")
-
-        self.__new_section(name)
-
-        if comment is not None:
-            self.__get_section().add_comment(comment)
-
-    def __parse_comment(self, line: str) -> None:
-        if line[0] != "#":
-            return
-
-        line = line[1:]
-
-        if len(line) == 0:
-            self.__get_section().add_comment("")
-            return
-
-        if line[0] == " ":
-            line = line[1:]
-
-        self.__get_section().add_comment(line)
-

+ 0 - 99
src/CxPini/pini.py

@@ -1,99 +0,0 @@
-from .section import section
-from .exception import pini_exception
-
-class pini:
-    def __init__(self):
-        self.__sections = list()
-
-    def get(self, target: str) -> section | None:
-        if type(target) is not str:
-            raise TypeError("Name of the section to get must be str.")
-
-        for count in self.__sections:
-            if count.name == target:
-                return count
-        
-        return None
-
-    def exists(self, target: str) -> bool:
-        return self.get(target) is not None
-
-    @property
-    def global_section(self) -> section | None:
-        for count in self.__sections:
-            if count.is_global:
-                return count
-
-        return None
-
-    @property
-    def has_global(self) -> bool:
-        return self.global_section is not None
-
-    def add(self, target: section) -> None:
-        if type(target) is not section:
-            raise TypeError("New section must be in section type.")
-
-        if target.is_global and not self.has_global:
-            self.__sections.append(target)
-            return
-
-        if not target.is_global and not self.exists(target.name):
-            self.__sections.append(target)
-            return 
-
-        if target.is_global:
-            raise pini_exception("This config already has global section.")
-
-        raise pini_exception("Section " + target.name + " already exists.")
-
-    def drop_global(self) -> section | None:
-        index = -1
-
-        for count in range(len(self.__sections)):
-            if self.__sections[count].is_global:
-                index = count
-                break
-
-        if index == -1:
-            return self.__sections.pop(index)
-
-        return
-
-    def drop(self, target: section | str) -> section | None:
-        if type(target) is section and target.is_global:
-            return self.drop_global()
-
-        if type(target) is section:
-            target = target.name
-
-        if type(target) is not str:
-            raise TypeError("Section to drop must be str or section.")
-        
-        index = -1
-
-        for count in range(len(self.__sections)):
-            if self.__sections[count].name == target:
-                index = count
-                break
-
-        if index != -1:
-            return self.__sections.pop(index)
-
-    def render(self) -> str:
-        summary = ""
-
-        if self.has_global:
-            summary = str(self.global_section)
-
-        for count in self.__sections:
-            if count.is_global:
-                continue 
-
-            summary = summary + str(count)
-
-        return summary
-
-    def __str__(self) -> str:
-        return self.render()
-            

+ 0 - 85
src/CxPini/section.py

@@ -1,85 +0,0 @@
-import textwrap
-
-from .exception import pini_exception
-from .key import key
-from .line import line
-
-class section(line):
-    def __init__(self):
-        super().__init__()
-        self.__keys: list[key] = list()
-
-    @property
-    def is_global(self) -> bool:
-        return self.name is None
-
-    def get(self, target: str) -> key | None:
-        if type(target) is not str:
-            raise TypeError("Key name to get must me str.")
-        
-        target.strip()
-
-        if len(target) == 0:
-            raise TypeError("Key name to get can not be empty.")
-
-        for count in self.__keys:
-            if count.name == target:
-                return count
-
-        return None
-
-    def exists(self, target: str) -> bool:
-        return self.get(target) is not None
-
-    def drop(self, target: key | str) -> key | None:
-        if type(target) is key:
-            target = target.name
-
-        index = -1
-
-        for count in range(len(self.__keys)):
-            if self.__keys[count].name == target:
-                index = count
-                break
-
-        if index != -1:
-            return self.__keys.pop(index)
-
-    def add(self, target: key) -> None:
-        if type(target) is not key:
-            raise TypeError("New key must be instance of key.")
-
-        if self.exists(target.name):
-            error = "Target key " 
-            error = error + target.name + " " 
-            error = error + "already exists."
-
-            raise pini_exception(error)
-
-        self.__keys.append(target.clone())
-
-    def render(self) -> str:
-        content = ""
-        comments = list()
-        
-        if self.name is not None:
-            content = "[" + self.name + "]\n"
-        
-        if self.comment is not None:
-            comments = textwrap.wrap(self.comment, 77)
-
-        for count in comments:
-            content = content + "# " + count + "\n"
-
-        if len(comments) > 0:
-            content = content + "\n"
-
-        for count in self.__keys:
-            content = content + str(count) + "\n"
-
-        if len(self.__keys) > 0:
-            content = content + "\n"
-
-        return content
-    
-        

+ 8 - 39
tests/01-key.py

@@ -10,45 +10,14 @@ sys.path.append(str(source_dir))
 
 import CxPini as pini
 
-check = pini.key()
-count = 0
+test = pini.key("sample")
+print(test)
 
-def dump():
-    global count
-    global check
+test.content = 10.5
+print(test)
 
-    count = count + 1
+test.comment = "Sample comment"
+print(test)
 
-    print("Dump: " + str(count))
-    print("Key: " + str(check.name))
-    print("Value: " + str(check.value))
-    print("Value type: " + str(check.value_type))
-    print("Comment: " + str(check.comment))
-    print("Result: ")
-    print(str(check))
-    print()
-
-
-dump()
-check.comment = "Sample"
-dump()
-check.name = "key"
-dump()
-check.value = "      value"
-dump()
-check.value = 10
-dump()
-check.name = None
-dump()
-check.name = "key"
-dump()
-print("Get result: " + str(check.get(20)))
-print()
-check.value = "other"
-dump()
-print("Get result: " + str(check.get("def")))
-print()
-check.value = None
-dump()
-print("Get result: " + str(check.get("def")))
-print()
+test.content = True
+print(test)

+ 18 - 0
tests/02-key-builder.py

@@ -0,0 +1,18 @@
+import sys
+import pathlib
+
+test_file = pathlib.Path(__file__).absolute()
+test_dir = test_file.parent
+project_dir = test_dir.parent
+source_dir = project_dir / "src"
+
+sys.path.append(str(source_dir))
+
+import CxPini as pini
+
+print(pini.key_builder("x=10"))
+print(pini.key_builder("    x     =   10     # Comment   "))
+print(pini.key_builder("x='10'"))
+print(pini.key_builder("x=true   #"))
+print(pini.key_builder("x=.05"))
+print(pini.key_builder("x=11.10"))

+ 0 - 53
tests/02-section.py

@@ -1,53 +0,0 @@
-import sys
-import pathlib
-
-test_file = pathlib.Path(__file__).absolute()
-test_dir = test_file.parent
-project_dir = test_dir.parent
-source_dir = project_dir / "src"
-
-sys.path.append(str(source_dir))
-
-import CxPini as pini
-
-first = pini.key()
-first.name = "first"
-first.value = 10
-
-second = pini.key()
-second.name = "other"
-second.value = "This is other key"
-
-last = pini.key()
-last.name = "last"
-last.value = 20
-last.comment = "And with comment"
-
-sample = pini.section()
-sample.add_comment("This is very basic library. ")
-sample.add_comment("It could be used as very very very nice. ")
-sample.add_comment("It is possible to split this very long comments auto.")
-sample.add_comment("I think it is very nice. You have not to do it Yourself.")
-sample.name = "Section"
-sample.add(first)
-sample.add(second)
-sample.add(last)
-
-print(sample)
-
-if sample.get("first").value != 10:
-    print("Can not get first.")
-
-if not sample.exists("other"):
-    print("Second can not be found.")
-
-if sample.get("last").value != 20:
-    print("Last return bad content.")
-
-if sample.exists("jiojoijio"):
-    print("Key which not exists is returned.")
-
-sample.drop(last)
-sample.drop("other")
-
-print(sample)

+ 0 - 52
tests/03-pini.py

@@ -1,52 +0,0 @@
-import sys
-import pathlib
-
-test_file = pathlib.Path(__file__).absolute()
-test_dir = test_file.parent
-project_dir = test_dir.parent
-source_dir = project_dir / "src"
-
-sys.path.append(str(source_dir))
-
-import CxPini as pini
-
-f1 = pini.key()
-f1.name = "x"
-f1.value = 20
-
-f2 = pini.key()
-f2.name = "y"
-f2.value = 30
-
-f3 = pini.key()
-f3.name = "z"
-
-f4 = pini.key()
-f4.name = "a"
-f4.value = "VALUE"
-f4.comment = "This is key comment."
-
-global_section = pini.section()
-global_section.comment = "This is global section."
-global_section.add(f2)
-global_section.add(f1)
-
-other_section = pini.section()
-other_section.name = "Other"
-other_section.comment = "This is other section."
-other_section.add(f3)
-other_section.add(f4)
-other_section.add(f1)
-
-last_section = pini.section()
-last_section.name = "last"
-last_section.comment = "This is very last section."
-last_section.add(f1)
-last_section.add(f2)
-
-test = pini.pini()
-test.add(global_section)
-test.add(other_section)
-test.add(last_section)
-
-print(test)