improve code quality add unittest
refactoring of for loops by introducing Walker class complete type checks
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
eclipse.preferences.version=1
|
||||
encoding//src/pysimpleini/document.py=utf-8
|
||||
encoding//src/pysimpleini/exceptions.py=utf-8
|
||||
encoding//src/pysimpleini/keys.py=utf-8
|
||||
encoding//src/pysimpleini/sections.py=utf-8
|
||||
encoding//src/pysimpleini/simpleini.py=utf-8
|
||||
encoding//src/pysimpleini/tools.py=utf-8
|
||||
encoding/<project>=UTF-8
|
||||
|
||||
@@ -28,8 +28,11 @@ from .exceptions import (
|
||||
PySimpleINI_WrongFormatException,
|
||||
)
|
||||
|
||||
|
||||
from .keys import PySimpleINI_key_Base, PySimpleINI_key_Blank, PySimpleINI_key_Comment, PySimpleINI_key
|
||||
|
||||
|
||||
from .simpleini import PySimpleINI
|
||||
from .sections import (
|
||||
PySimpleINI_Section_Base,
|
||||
PySimpleINI_Section_Blank,
|
||||
@@ -37,5 +40,3 @@ from .sections import (
|
||||
PySimpleINI_Section,
|
||||
PySimpleINI_Section_Root,
|
||||
)
|
||||
|
||||
from .simpleini import PySimpleINI
|
||||
|
||||
@@ -45,13 +45,17 @@ class PySimpleINI_Document:
|
||||
|
||||
@property
|
||||
def sections(self):
|
||||
"""sections getter"""
|
||||
return self._sections
|
||||
|
||||
def reset_sections(self):
|
||||
"""reset sections list"""
|
||||
self._sections = []
|
||||
|
||||
|
||||
class PySimpleINI_Document_Parser(PySimpleINI_Document):
|
||||
"""Document Parser class"""
|
||||
|
||||
def __init__(self, bStrict: bool = False) -> None:
|
||||
super().__init__()
|
||||
self._lastsection: Optional[PySimpleINI_Section] = None
|
||||
@@ -136,6 +140,8 @@ class PySimpleINI_Document_Parser(PySimpleINI_Document):
|
||||
|
||||
|
||||
class PySimpleINI_Document_Formater(PySimpleINI_Document):
|
||||
"""Document formater class"""
|
||||
|
||||
def format(self, bBeautify: bool = False, bWipeComments: bool = False) -> str:
|
||||
"""Generate the full formated output.
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ class PySimpleINI_key_Base(metaclass=ABCMeta):
|
||||
Returns:
|
||||
requested value
|
||||
"""
|
||||
pass
|
||||
|
||||
def format(self) -> str:
|
||||
"""Key value formater (renderer)
|
||||
|
||||
@@ -17,8 +17,9 @@ from typing import TYPE_CHECKING
|
||||
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
from .keys import PySimpleINI_key_Base, PySimpleINI_key_Blank, PySimpleINI_key_Comment, PySimpleINI_key
|
||||
from .exceptions import PySimpleINI_KeyNotFoundException
|
||||
from .tools import Walker
|
||||
from .keys import PySimpleINI_key_Base, PySimpleINI_key_Blank, PySimpleINI_key_Comment, PySimpleINI_key
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover # Only imports the below statements during type checking
|
||||
from typing import List, Optional
|
||||
@@ -199,28 +200,10 @@ class PySimpleINI_Section(PySimpleINI_Section_Comment, PySimpleINI_Section_Blank
|
||||
Returns:
|
||||
Number of deleted keys
|
||||
"""
|
||||
keys = self.getkey(name)
|
||||
|
||||
if isinstance(keys, PySimpleINI_key):
|
||||
keys = [keys]
|
||||
i = 0
|
||||
ndeleted: int = 0
|
||||
for key in keys:
|
||||
if isinstance(key, PySimpleINI_key):
|
||||
indexok = True
|
||||
if index is not None:
|
||||
indexok = False
|
||||
if index == i:
|
||||
indexok = True
|
||||
valueok = True
|
||||
if value is not None:
|
||||
valueok = False
|
||||
if value == key.getvalue():
|
||||
valueok = True
|
||||
if indexok and valueok:
|
||||
self.keys.remove(key)
|
||||
ndeleted = ndeleted + 1
|
||||
i = i + 1
|
||||
walker = Walker[PySimpleINI_key_Base](targetname=name, targetindex=index, targetvalue=value, targettypes=PySimpleINI_key)
|
||||
walker.walk(self.keys, self.keys.remove)
|
||||
ndeleted: int = walker.num_matched
|
||||
|
||||
if ndeleted == 0:
|
||||
raise PySimpleINI_KeyNotFoundException()
|
||||
@@ -250,22 +233,8 @@ class PySimpleINI_Section(PySimpleINI_Section_Comment, PySimpleINI_Section_Blank
|
||||
|
||||
result: List[PySimpleINI_key] = []
|
||||
|
||||
i = 0
|
||||
for key in self.keys:
|
||||
if isinstance(key, PySimpleINI_key) and (name == key.getname()):
|
||||
indexok = True
|
||||
if index is not None:
|
||||
indexok = False
|
||||
if index == i:
|
||||
indexok = True
|
||||
valueok = True
|
||||
if value is not None:
|
||||
valueok = False
|
||||
if value == key.getvalue():
|
||||
valueok = True
|
||||
if indexok and valueok:
|
||||
result.append(key)
|
||||
i = i + 1
|
||||
walker = Walker[PySimpleINI_key_Base](targetname=name, targetindex=index, targetvalue=value, targettypes=PySimpleINI_key)
|
||||
walker.walk(self.keys, result.append)
|
||||
|
||||
if len(result) == 0:
|
||||
raise PySimpleINI_KeyNotFoundException()
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# PySimpleINI (c) by chacha
|
||||
#
|
||||
# PySimpleINI is licensed under a
|
||||
@@ -22,7 +21,8 @@ from typing import TYPE_CHECKING
|
||||
from pathlib import Path
|
||||
|
||||
from .document import PySimpleINI_Document_Parser, PySimpleINI_Document_Formater
|
||||
from .sections import PySimpleINI_Section, PySimpleINI_Section_Comment, PySimpleINI_Section_Blank
|
||||
from .tools import Walker
|
||||
from .sections import PySimpleINI_Section, PySimpleINI_Section_Comment, PySimpleINI_Section_Blank, PySimpleINI_Section_Base
|
||||
from .keys import PySimpleINI_key
|
||||
from .exceptions import PySimpleINI_SectionNotFoundException, PySimpleINI_KeyNotFoundException
|
||||
|
||||
@@ -60,21 +60,6 @@ class PySimpleINI(PySimpleINI_Document_Parser, PySimpleINI_Document_Formater):
|
||||
"""
|
||||
return self._filepath
|
||||
|
||||
def parsefile(self, filepath: Optional[Path | str]) -> PySimpleINI:
|
||||
"""Parse a file.
|
||||
|
||||
Arg:
|
||||
filepath: path of the file to parse
|
||||
Returns:
|
||||
self for convinience
|
||||
"""
|
||||
self._filepath = None
|
||||
if isinstance(filepath, (str, Path)):
|
||||
self._filepath = Path(filepath)
|
||||
with open(self._filepath, encoding="utf-8") as file:
|
||||
self.parse(file.read())
|
||||
return self
|
||||
|
||||
@filepath.setter
|
||||
def filepath(self, filepath: Optional[Path | str]) -> None:
|
||||
"""Change / Set the INI file path.
|
||||
@@ -90,6 +75,21 @@ class PySimpleINI(PySimpleINI_Document_Parser, PySimpleINI_Document_Formater):
|
||||
if isinstance(filepath, (str, Path)):
|
||||
self._filepath = Path(filepath)
|
||||
|
||||
def parsefile(self, filepath: Optional[Path | str]) -> PySimpleINI:
|
||||
"""Parse a file.
|
||||
|
||||
Arg:
|
||||
filepath: path of the file to parse
|
||||
Returns:
|
||||
self for convinience
|
||||
"""
|
||||
self._filepath = None
|
||||
if isinstance(filepath, (str, Path)):
|
||||
self._filepath = Path(filepath)
|
||||
with open(self._filepath, encoding="utf-8") as file:
|
||||
self.parse(file.read())
|
||||
return self
|
||||
|
||||
def delsection(self, name: str, index: Optional[int] = None) -> int:
|
||||
"""Delete an existing Section.
|
||||
|
||||
@@ -105,24 +105,14 @@ class PySimpleINI(PySimpleINI_Document_Parser, PySimpleINI_Document_Formater):
|
||||
Returns:
|
||||
Number of deleted sections
|
||||
"""
|
||||
sections = self.getsection(name)
|
||||
if isinstance(sections, PySimpleINI_Section):
|
||||
_sections = []
|
||||
_sections.append(sections)
|
||||
sections = _sections
|
||||
i = 0
|
||||
ndeleted = 0
|
||||
for section in sections:
|
||||
if isinstance(section, PySimpleINI_Section):
|
||||
indexok = True
|
||||
if index is not None:
|
||||
indexok = False
|
||||
if index == i:
|
||||
indexok = True
|
||||
if indexok:
|
||||
self.sections.remove(section)
|
||||
ndeleted = ndeleted + 1
|
||||
i = i + 1
|
||||
|
||||
walker = Walker[PySimpleINI_Section_Base](targetname=name, targetindex=index, targettypes=PySimpleINI_Section)
|
||||
walker.walk(self.sections, self.sections.remove)
|
||||
ndeleted: int = walker.num_matched
|
||||
|
||||
if ndeleted == 0:
|
||||
raise PySimpleINI_SectionNotFoundException()
|
||||
|
||||
return ndeleted
|
||||
|
||||
def addsection(self, name: str) -> PySimpleINI_Section:
|
||||
@@ -146,7 +136,7 @@ class PySimpleINI(PySimpleINI_Document_Parser, PySimpleINI_Document_Formater):
|
||||
The new section
|
||||
"""
|
||||
section = PySimpleINI_Section_Comment(-1)
|
||||
section.appendCommentKey(self.sectioncomment_tags[0], self.sectioncomment_tags[0], -1)
|
||||
section.appendCommentKey(self.sectioncomment_tags[0], value, -1)
|
||||
self.sections.append(section)
|
||||
return section
|
||||
|
||||
@@ -179,9 +169,10 @@ class PySimpleINI(PySimpleINI_Document_Parser, PySimpleINI_Document_Formater):
|
||||
List of section names.
|
||||
"""
|
||||
result = []
|
||||
for section in self.sections:
|
||||
if isinstance(section, PySimpleINI_Section):
|
||||
result.append(section.getname())
|
||||
|
||||
walker = Walker[PySimpleINI_Section_Base](targettypes=PySimpleINI_Section)
|
||||
walker.walk(self.sections, lambda x: result.append(x.getname()))
|
||||
|
||||
return result
|
||||
|
||||
def getsection(self, name: str) -> PySimpleINI_Section | list[PySimpleINI_Section]:
|
||||
@@ -199,10 +190,8 @@ class PySimpleINI(PySimpleINI_Document_Parser, PySimpleINI_Document_Formater):
|
||||
|
||||
result: List[PySimpleINI_Section] = []
|
||||
|
||||
for section in self.sections:
|
||||
if isinstance(section, PySimpleINI_Section):
|
||||
if name == section.getname():
|
||||
result.append(section)
|
||||
walker = Walker[PySimpleINI_Section_Base](targetname=name, targettypes=PySimpleINI_Section)
|
||||
walker.walk(self.sections, result.append)
|
||||
|
||||
if len(result) == 0:
|
||||
raise PySimpleINI_SectionNotFoundException()
|
||||
@@ -223,14 +212,11 @@ class PySimpleINI(PySimpleINI_Document_Parser, PySimpleINI_Document_Formater):
|
||||
Returns:
|
||||
list of Keys names
|
||||
"""
|
||||
sections = self.getsection(sectionName)
|
||||
if isinstance(sections, PySimpleINI_Section):
|
||||
return sections.getallkeynames()
|
||||
|
||||
result = []
|
||||
for section in sections:
|
||||
if isinstance(section, PySimpleINI_Section):
|
||||
result.extend(section.getallkeynames())
|
||||
|
||||
walker = Walker[PySimpleINI_Section_Base](targetname=sectionName, targettypes=PySimpleINI_Section)
|
||||
walker.walk(self.sections, lambda x: result.extend(x.getallkeynames()))
|
||||
|
||||
return result
|
||||
|
||||
def delkey(self, sectionName: str, keyName: str) -> int:
|
||||
@@ -267,24 +253,28 @@ class PySimpleINI(PySimpleINI_Document_Parser, PySimpleINI_Document_Formater):
|
||||
Returns:
|
||||
Number of deleted keys
|
||||
"""
|
||||
sections = self.getsection(sectionName)
|
||||
if isinstance(sections, PySimpleINI_Section):
|
||||
_sections = []
|
||||
_sections.append(sections)
|
||||
sections = _sections
|
||||
|
||||
ndeleted = 0
|
||||
for section in sections:
|
||||
if isinstance(section, PySimpleINI_Section):
|
||||
walkersection = Walker[PySimpleINI_Section_Base](targetname=sectionName, targettypes=PySimpleINI_Section)
|
||||
|
||||
class CallbackWalkerSection:
|
||||
"""temporary class to wrap ndeleted"""
|
||||
|
||||
ndeleted: int = 0
|
||||
|
||||
@classmethod
|
||||
def CallbackWalkerSection(cls, section):
|
||||
"""Walker callback"""
|
||||
try:
|
||||
ndeleted = ndeleted + section.delkey(keyName, index, value)
|
||||
cls.ndeleted = cls.ndeleted + section.delkey(keyName, index, value)
|
||||
except PySimpleINI_KeyNotFoundException:
|
||||
pass
|
||||
|
||||
if ndeleted == 0:
|
||||
walkersection.walk(self.sections, CallbackWalkerSection.CallbackWalkerSection)
|
||||
|
||||
if CallbackWalkerSection.ndeleted == 0:
|
||||
raise PySimpleINI_KeyNotFoundException()
|
||||
|
||||
return ndeleted
|
||||
return CallbackWalkerSection.ndeleted
|
||||
|
||||
def getkey(self, sectionName: str, keyName: str) -> PySimpleINI_key | List[PySimpleINI_key]:
|
||||
"""Get one or more Key from Section.
|
||||
@@ -322,27 +312,35 @@ class PySimpleINI(PySimpleINI_Document_Parser, PySimpleINI_Document_Formater):
|
||||
Returns:
|
||||
Found Keys
|
||||
"""
|
||||
sections = self.getsection(sectionName)
|
||||
if isinstance(sections, PySimpleINI_Section):
|
||||
return sections.getkey_ex(keyName, index, value, self._bForceAlwaysOutputArrays)
|
||||
|
||||
result = []
|
||||
for section in sections:
|
||||
if isinstance(section, PySimpleINI_Section):
|
||||
walkersection = Walker[PySimpleINI_Section_Base](targetname=sectionName, targettypes=PySimpleINI_Section)
|
||||
|
||||
class CallbackWalkerSection:
|
||||
"""temporary class to wrap result"""
|
||||
|
||||
result: List[PySimpleINI_key] = []
|
||||
bForceAlwaysOutputArrays: bool = self._bForceAlwaysOutputArrays
|
||||
|
||||
@classmethod
|
||||
def CallbackWalkerSection(cls, section):
|
||||
"""Walker callback"""
|
||||
try:
|
||||
keys = section.getkey_ex(keyName, index, value, self._bForceAlwaysOutputArrays)
|
||||
keys = section.getkey_ex(keyName, index, value, cls.bForceAlwaysOutputArrays)
|
||||
if isinstance(keys, PySimpleINI_key):
|
||||
result.append(keys)
|
||||
cls.result.append(keys)
|
||||
else: # array
|
||||
result.extend(keys)
|
||||
cls.result.extend(keys)
|
||||
except PySimpleINI_KeyNotFoundException:
|
||||
pass
|
||||
if len(result) == 0:
|
||||
|
||||
walkersection.walk(self.sections, CallbackWalkerSection.CallbackWalkerSection)
|
||||
|
||||
if len(CallbackWalkerSection.result) == 0:
|
||||
raise PySimpleINI_KeyNotFoundException()
|
||||
|
||||
if len(result) == 1 and (not self._bForceAlwaysOutputArrays):
|
||||
return result[0]
|
||||
return result
|
||||
if len(CallbackWalkerSection.result) == 1 and (not self._bForceAlwaysOutputArrays):
|
||||
return CallbackWalkerSection.result[0]
|
||||
return CallbackWalkerSection.result
|
||||
|
||||
def getkeyvalue(self, sectionName: str, keyName: str) -> str | list[str]:
|
||||
"""Get one or more Key's values from Section.
|
||||
|
||||
123
src/pysimpleini/tools.py
Normal file
123
src/pysimpleini/tools.py
Normal file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# PySimpleINI (c) by chacha
|
||||
#
|
||||
# PySimpleINI is licensed under a
|
||||
# Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Unported License.
|
||||
#
|
||||
# You should have received a copy of the license along with this
|
||||
# work. If not, see <https://creativecommons.org/licenses/by-nc-sa/4.0/>.
|
||||
|
||||
"""Tools module
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, TypeVar, Generic, Protocol, runtime_checkable
|
||||
|
||||
from abc import abstractmethod
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover # Only imports the below statements during type checking
|
||||
from typing import Optional, Callable, List
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class ProtoGetVal(Protocol):
|
||||
"""generic protocol definition"""
|
||||
|
||||
@abstractmethod
|
||||
def getvalue(self) -> str:
|
||||
"""generic protocol method"""
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class ProtoGetName(Protocol):
|
||||
"""generic protocol definition"""
|
||||
|
||||
@abstractmethod
|
||||
def getname(self) -> str:
|
||||
"""generic protocol method"""
|
||||
|
||||
|
||||
T_Elem = TypeVar("T_Elem")
|
||||
|
||||
|
||||
class Walker(Generic[T_Elem]):
|
||||
"""Generic walker class to search key or section in a list"""
|
||||
|
||||
def __init__(self, **kwargs) -> None:
|
||||
|
||||
self.targetname: Optional[str] = None
|
||||
if "targetname" in kwargs:
|
||||
self.targetname = kwargs["targetname"]
|
||||
|
||||
self.targetvalue: Optional[str] = None
|
||||
if "targetvalue" in kwargs:
|
||||
self.targetvalue = kwargs["targetvalue"]
|
||||
|
||||
self.targetindex: Optional[str] = None
|
||||
if "targetindex" in kwargs:
|
||||
self.targetindex = kwargs["targetindex"]
|
||||
|
||||
self.targettypes: List[type[T_Elem]] = []
|
||||
if "targettypes" in kwargs:
|
||||
if isinstance(kwargs["targettypes"], list):
|
||||
self.targettypes = kwargs["targettypes"]
|
||||
else:
|
||||
self.targettypes = [kwargs["targettypes"]]
|
||||
|
||||
self.index: int = 0
|
||||
self.num_matched = 0
|
||||
|
||||
def reset(self) -> None:
|
||||
"""Reset walker class context"""
|
||||
self.index = 0
|
||||
self.num_matched = 0
|
||||
|
||||
def _checkname(self, elem: T_Elem) -> bool:
|
||||
if isinstance(elem, ProtoGetName):
|
||||
return bool((self.targetname is None) or (elem.getname() == self.targetname))
|
||||
return True
|
||||
|
||||
def _checkindex(self) -> bool:
|
||||
if self.targetindex is not None:
|
||||
self.index = self.index + 1
|
||||
return bool(self.targetindex == (self.index - 1))
|
||||
return True
|
||||
|
||||
def _checkvalue(self, elem: T_Elem) -> bool:
|
||||
if isinstance(elem, ProtoGetVal):
|
||||
return bool((self.targetvalue is None) or (elem.getvalue() == self.targetvalue))
|
||||
return True
|
||||
|
||||
def walk(self, walklist: List[T_Elem], action: Callable) -> None:
|
||||
"""walk a list using defined criteria
|
||||
|
||||
Args:
|
||||
walklist: the list to walk on
|
||||
action: a callable appyed to the found elements
|
||||
"""
|
||||
self.reset()
|
||||
for elem in walklist:
|
||||
# checking elem type
|
||||
if len(self.targettypes) != 0:
|
||||
bFound = False
|
||||
for targettype in self.targettypes:
|
||||
if isinstance(elem, targettype):
|
||||
bFound = True
|
||||
break
|
||||
if not bFound:
|
||||
continue
|
||||
|
||||
if not self._checkname(elem):
|
||||
continue
|
||||
|
||||
if not self._checkindex():
|
||||
continue
|
||||
|
||||
if not self._checkvalue(elem):
|
||||
continue
|
||||
|
||||
action(elem)
|
||||
self.num_matched = self.num_matched + 1
|
||||
if self.targetindex is not None:
|
||||
return
|
||||
@@ -108,7 +108,7 @@ class Test_PySimpleINI_base(unittest.TestCase):
|
||||
|
||||
def test_deletekey(self):
|
||||
# create copy of the file
|
||||
testini = PySimpleINI(testdir_path / "testfiles/test_delete.ini")
|
||||
testini = PySimpleINI(testdir_path / "testfiles/test_deleteKey.ini")
|
||||
testini.filepath = testdir_path / "tmp/out.ini"
|
||||
testini.writefile(False)
|
||||
|
||||
@@ -157,7 +157,7 @@ class Test_PySimpleINI_base(unittest.TestCase):
|
||||
|
||||
def test_deletesection(self):
|
||||
# create copy of the file
|
||||
testini = PySimpleINI(testdir_path / "testfiles/test_delete.ini")
|
||||
testini = PySimpleINI(testdir_path / "testfiles/test_deleteSection.ini")
|
||||
testini.filepath = testdir_path / "tmp/out.ini"
|
||||
testini.writefile(False)
|
||||
|
||||
@@ -195,7 +195,7 @@ class Test_PySimpleINI_base(unittest.TestCase):
|
||||
def test_deletekey_fromfile(self):
|
||||
|
||||
# create copy of the file
|
||||
testini = PySimpleINI(testdir_path / "testfiles/test_delete.ini")
|
||||
testini = PySimpleINI(testdir_path / "testfiles/test_deleteSection.ini")
|
||||
testini.filepath = testdir_path / "tmp/out.ini"
|
||||
testini.writefile(False)
|
||||
|
||||
@@ -230,7 +230,7 @@ class Test_PySimpleINI_base(unittest.TestCase):
|
||||
testinitmp.delkey_ex("testsection1", "key2", 1, "test2")
|
||||
|
||||
# create copy of the file
|
||||
testini = PySimpleINI(testdir_path / "testfiles/test_delete.ini")
|
||||
testini = PySimpleINI(testdir_path / "testfiles/test_deleteSection.ini")
|
||||
testini.filepath = testdir_path / "tmp/out.ini"
|
||||
testini.writefile(False)
|
||||
|
||||
|
||||
@@ -8,5 +8,4 @@ key2=test3
|
||||
key1=test1
|
||||
key2=test2
|
||||
key2=test3
|
||||
key2=test2
|
||||
|
||||
key2=test2
|
||||
14
test/testfiles/test_deleteSection.ini
Normal file
14
test/testfiles/test_deleteSection.ini
Normal file
@@ -0,0 +1,14 @@
|
||||
[testsection1]
|
||||
|
||||
key1=test1
|
||||
key2=test2
|
||||
key2=test3
|
||||
|
||||
[testsection2]
|
||||
key1=test1
|
||||
key2=test2
|
||||
key2=test3
|
||||
key2=test2
|
||||
|
||||
[testsection2]
|
||||
key6=test6
|
||||
Reference in New Issue
Block a user