From c2947fa268374ad0b5ea8234c35ab6a720a6c0ac Mon Sep 17 00:00:00 2001 From: cclecle Date: Sat, 23 Sep 2023 12:19:01 +0100 Subject: [PATCH 01/13] fix: update copier api --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 0c4181a..13ee83d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -341,7 +341,7 @@ pipeline { |'''.strip() | |import copier - |copier.run_auto("./", "../_gitrepo",vcs_ref="HEAD",use_prereleases=True,defaults=True,cleanup_on_error=False) + |copier.run_copy("./", "../_gitrepo",vcs_ref="HEAD",use_prereleases=True,defaults=True,cleanup_on_error=False) | |__EOWRAPPER__ """.stripMargin()) -- 2.47.3 From fd92ff2e913b6a684440f8387c6cdbc367a9c7b5 Mon Sep 17 00:00:00 2001 From: cclecle Date: Sat, 23 Sep 2023 12:20:23 +0100 Subject: [PATCH 02/13] chore: remove sample unittest function --- test/test_ut99.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/test/test_ut99.py b/test/test_ut99.py index 6c00d88..830fb1a 100644 --- a/test/test_ut99.py +++ b/test/test_ut99.py @@ -7,26 +7,15 @@ # work. If not, see . import unittest -from io import StringIO -from contextlib import redirect_stdout,redirect_stderr +from io import StringIO +from contextlib import redirect_stdout, redirect_stderr print(__name__) print(__package__) from src import pygamecfg + class Testtest_module(unittest.TestCase): def test_version(self): - self.assertNotEqual(pygamecfg.__version__,"?.?.?") - - def test_test_module(self): - - with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: - self.assertEqual(pygamecfg.test_function(41),42) - - self.assertEqual(len(capted_stderr.getvalue()),0) - self.assertEqual(capted_stdout.getvalue().strip(),"Hello world !") - self.assertEqual(len(capted_stderr.getvalue()),0) - - - \ No newline at end of file + self.assertNotEqual(pygamecfg.__version__, "?.?.?") -- 2.47.3 From 5c2b08c86548fe711d156d15fcebd74d6c2f724f Mon Sep 17 00:00:00 2001 From: cclecle Date: Sat, 23 Sep 2023 15:44:31 +0100 Subject: [PATCH 03/13] test: fix unittest framework + add ut99 and gen unittests --- src/pygamecfg/__init__.py | 2 + src/pygamecfg/__main__.py | 85 ++-- src/pygamecfg/core.py | 6 +- src/pygamecfg/ut2k4.py | 1 - src/pygamecfg/ut99.py | 16 +- test/data/UT99/System/UT99.ini | 520 +++++++++++++++++++++ test/data/UT99/System/UnrealTournament.ini | 520 +++++++++++++++++++++ test/test_gen.py | 33 ++ test/test_ut99.py | 297 +++++++++++- 9 files changed, 1431 insertions(+), 49 deletions(-) create mode 100644 test/data/UT99/System/UT99.ini create mode 100644 test/data/UT99/System/UnrealTournament.ini create mode 100644 test/test_gen.py diff --git a/src/pygamecfg/__init__.py b/src/pygamecfg/__init__.py index e1661c2..80ca5b9 100644 --- a/src/pygamecfg/__init__.py +++ b/src/pygamecfg/__init__.py @@ -15,6 +15,8 @@ import warnings from .core import GameOptions_Factory +from . import ut99, ut2k4, cod4 + try: # pragma: no cover __version__ = version("pygamecfg") except PackageNotFoundError: # pragma: no cover diff --git a/src/pygamecfg/__main__.py b/src/pygamecfg/__main__.py index 60680b5..3478d88 100644 --- a/src/pygamecfg/__main__.py +++ b/src/pygamecfg/__main__.py @@ -11,51 +11,58 @@ """CLI interface module""" from __future__ import annotations +import sys from argparse import ArgumentParser from . import __Summuary__, __Name__ from . import GameOptions_Factory -parser = ArgumentParser(description=__Summuary__) -parser.add_argument("-v", "--verbosity", action="count", default=0, help="increase output verbosity") -parser.add_argument("-b", "--basegamedir", help="set the game base dir", default="./") -parser.add_argument("-c", "--configfile", help="set the default config file", default="") -parser.add_argument("-g", "--game", help="the target game", choices=["ut99", "cod4"]) -subparsers = parser.add_subparsers(dest="command", help="command type", required=True) +def fct_main(args: list[str]) -> int: + parser = ArgumentParser(prog=__Name__, description=__Summuary__) + parser.add_argument("-v", "--verbosity", action="count", default=0, help="increase output verbosity") -SetOption_subparser = subparsers.add_parser("SetOption", help="Set/Add a game config file option value (may need reboot)") -SetOption_subparser.add_argument("option") -SetOption_subparser.add_argument("value", nargs="?") + parser.add_argument("-b", "--basegamedir", help="set the game base dir", default="./") + parser.add_argument("-c", "--configfile", help="set the default config file", default="") + parser.add_argument("-g", "--game", help="the target game", choices=["ut99", "cod4", "ut2k4"], required=True) + subparsers = parser.add_subparsers(dest="command", help="command type", required=True) + + SetOption_subparser = subparsers.add_parser("SetOption", help="Set/Add a game config file option value (may need reboot)") + SetOption_subparser.add_argument("option") + SetOption_subparser.add_argument("value", nargs="?") + + RemOption_subparser = subparsers.add_parser("RemOption", help="Remove a game config file option, w or w/o value (may need reboot)") + RemOption_subparser.add_argument("option") + RemOption_subparser.add_argument("value", nargs="?") + + GetOption_subparser = subparsers.add_parser("GetOption", help="Get a game config file option value") + GetOption_subparser.add_argument("option") + + args = parser.parse_args(args) + + if args.verbosity: + print("Using base game dir: {0}".format(args.basegamedir)) + print("Using config file: {0}".format(args.configfile)) + + if args.configfile == "": + if args.game == "ut99": + args.configfile = "./System/UnrealTournament.ini" + if args.game == "ut2k4": + args.configfile = "./System/UT2004.ini" + elif args.game == "cod4": + args.configfile = "./main/server.cfg" + _GameOptions = GameOptions_Factory(args.game, args.basegamedir, args.configfile) + + if args.command == "SetOption": + _GameOptions.set(args.option, args.value) + elif args.command == "RemOption": + _GameOptions.rem(args.option, args.value) + elif args.command == "GetOption": + res = _GameOptions.get(args.option) + print(res) + else: + raise RuntimeError("Invalid argument") -RemOption_subparser = subparsers.add_parser("RemOption", help="Remove a game config file option, w or w/o value (may need reboot)") -RemOption_subparser.add_argument("option") -RemOption_subparser.add_argument("value", nargs="?") - -GetOption_subparser = subparsers.add_parser("GetOption", help="Get a game config file option value") -GetOption_subparser.add_argument("option") - -args = parser.parse_args() - -if args.verbosity: - print("Using base game dir: {0}".format(args.basegamedir)) - print("Using config file: {0}".format(args.configfile)) - -if args.configfile == "": - if args.game == "ut99": - args.configfile = "./System/UnrealTournament.ini" - elif args.game == "cod4": - args.configfile = "./main/server.cfg" -_GameOptions = GameOptions_Factory(args.game, args.basegamedir, args.configfile) - - -if args.command == "SetOption": - _GameOptions.set(args.option, args.value) -elif args.command == "RemOption": - _GameOptions.rem(args.option, args.value) -elif args.command == "GetOption": - res = _GameOptions.get(args.option) - print(res) -else: - raise RuntimeError("Invalid argument") +if __name__ == "__main__": + fct_main(sys.argv[1:]) diff --git a/src/pygamecfg/core.py b/src/pygamecfg/core.py index 9c6d921..b2a1f44 100644 --- a/src/pygamecfg/core.py +++ b/src/pygamecfg/core.py @@ -36,7 +36,7 @@ class GameOption(metaclass=ABCMeta): intval = int(value) self.szFormatedValue = str(bool(intval)) except: - self.szFormatedValue = str(bool(1)) if value.lower() == "true" else str(bool(0)) + self.szFormatedValue = str(True) if str(value).lower() == "true" else str(False) elif self.TValueType == OptionType.OT_FLOAT: self.szFormatedValue = str(float(value)) else: @@ -63,7 +63,7 @@ class GameOptions_Factory: def __init__(self, szGameType: str, GameRootDir: str, ConfigFileRelPath: Union[None, str] = None) -> None: self.szGameType = szGameType self.ar_Options = [] - for Options_cls in self.ar_Options_cls: + for Options_cls in GameOptions_Factory.ar_Options_cls: if Options_cls.szGameType == szGameType: self.ar_Options.append(Options_cls(GameRootDir, ConfigFileRelPath)) @@ -72,7 +72,7 @@ class GameOptions_Factory: cls.ar_Options_cls.append(Option) def set(self, OptionName: str, value: str) -> None: - for _option in self.ar_Options: + for _option in GameOptions_Factory.ar_Options: if _option.szOptionName == OptionName: _option.set(value) return diff --git a/src/pygamecfg/ut2k4.py b/src/pygamecfg/ut2k4.py index 296a09f..6ac772c 100644 --- a/src/pygamecfg/ut2k4.py +++ b/src/pygamecfg/ut2k4.py @@ -67,7 +67,6 @@ class GameOption_UT2k4(GameOption): def get(self) -> Union[None, str]: if not self.szOptionName: raise RuntimeError("szOptionName not set") - print("get option <{0}>".format(self.szOptionName)) res = self.inifile.getKeyValue(self.szSectionName, self.szKeyName) return res diff --git a/src/pygamecfg/ut99.py b/src/pygamecfg/ut99.py index 5ebab08..9f96423 100644 --- a/src/pygamecfg/ut99.py +++ b/src/pygamecfg/ut99.py @@ -61,14 +61,13 @@ class GameOption_UT99(GameOption): raise RuntimeError("szOptionName is not set") if not self.bRemovable: raise RuntimeError("this options is not removable") - self.inifile.delKeyEx(self.szSectionName, self.szKeyName, None, value) + self.inifile.delkey_ex(self.szSectionName, self.szKeyName, None, value) self.inifile.writeFile() def get(self) -> Union[None, str]: if not self.szOptionName: raise RuntimeError("szOptionName not set") - # print("get option <{0}>".format(self.szOptionName)) - res = self.inifile.getKeyValue(self.szSectionName, self.szKeyName) + res = self.inifile.getkeyvalue(self.szSectionName, self.szKeyName) return res @@ -325,6 +324,17 @@ class GameOption_UT99_AdminPassword(GameOption_UT99): self.inifile.setAddKeyValue("UTServerAdmin.UTServerAdmin", "AdminPassword", value) self.inifile.writeFile() + def get(self) -> Union[None, str]: + try: + return super().get() + except KeyNotFoundError: + return self.inifile.getkeyvalue( + "UTServerAdmin.UTServerAdmin", + "AdminPassword", + ) + + raise NotImplementedError("method not implemented") + @GameOptions_Factory_Register class GameOption_UT99_GamePassword(GameOption_UT99): diff --git a/test/data/UT99/System/UT99.ini b/test/data/UT99/System/UT99.ini new file mode 100644 index 0000000..9a4f86c --- /dev/null +++ b/test/data/UT99/System/UT99.ini @@ -0,0 +1,520 @@ +[URL] +Protocol=unreal +ProtocolDescription=Unreal Protocol +Name=Player +Map=Index.unr +LocalMap=DM-Codex.unr +Host= +Portal= +MapExt=unr +SaveExt=usa +Port=7777 +Class=Botpack.TMale1 + +[FirstRun] +FirstRun=0 + +[PackageRemap] +UnrealShare=UnrealI + +[Engine.Engine] +GameRenderDevice=SoftDrv.SoftwareRenderDevice +AudioDevice=Galaxy.GalaxyAudioSubsystem +NetworkDevice=IpDrv.TcpNetDriver +DemoRecordingDevice=Engine.DemoRecDriver +Console=UTMenu.UTConsole +Language=int +GameEngine=Engine.GameEngine +EditorEngine=Editor.EditorEngine +WindowedRenderDevice=SoftDrv.SoftwareRenderDevice +RenderDevice=GlideDrv.GlideRenderDevice +DefaultGame=Botpack.DeathMatchPlus +DefaultServerGame=Botpack.DeathMatchPlus +ViewportManager=WinDrv.WindowsClient +Render=Render.Render +Input=Engine.Input +Canvas=Engine.Canvas + +[Core.System] +PurgeCacheDays=30 +SavePath=../Save +CachePath=../Cache +CacheExt=.uxx +Paths=../System/*.u +Paths=../Maps/*.unr +Paths=../Textures/*.utx +Paths=../Sounds/*.uax +Paths=../Music/*.umx +Suppress=DevLoad +Suppress=DevSave +Suppress=DevNetTraffic +Suppress=DevGarbage +Suppress=DevKill +Suppress=DevReplace +Suppress=DevSound +Suppress=DevCompile +Suppress=DevBind +Suppress=DevBsp + +[Engine.GameEngine] +CacheSizeMegs=4 +UseSound=True +ServerActors=IpDrv.UdpBeacon +ServerActors=IpServer.UdpServerQuery +ServerActors=IpServer.UdpServerUplink MasterServerAddress=unreal.epicgames.com MasterServerPort=27900 +ServerActors=IpServer.UdpServerUplink MasterServerAddress=master0.gamespy.com MasterServerPort=27900 +ServerActors=IpServer.UdpServerUplink MasterServerAddress=master.mplayer.com MasterServerPort=27900 +ServerActors=UWeb.WebServer +ServerPackages=SoldierSkins +ServerPackages=CommandoSkins +ServerPackages=FCommandoSkins +ServerPackages=SGirlSkins +ServerPackages=BossSkins +ServerPackages=Botpack + +[WinDrv.WindowsClient] +WindowedViewportX=640 +WindowedViewportY=480 +WindowedColorBits=16 +FullscreenViewportX=640 +FullscreenViewportY=480 +FullscreenColorBits=16 +Brightness=0.500000 +MipFactor=1.000000 +UseDirectDraw=True +UseJoystick=False +CaptureMouse=True +StartupFullscreen=True +CurvedSurfaces=False +LowDetailTextures=False +ScreenFlashes=True +NoLighting=False +SlowVideoBuffering=True +DeadZoneXYZ=True +DeadZoneRUV=False +InvertVertical=False +ScaleXYZ=1000.0 +ScaleRUV=2000.0 +MinDesiredFrameRate=30.0 +Decals=True +NoDynamicLights=False + +[XDrv.XClient] +WindowedViewportX=640 +WindowedViewportY=480 +WindowedColorBits=16 +FullscreenViewportX=640 +FullscreenViewportY=480 +FullscreenColorBits=16 +Brightness=0.500000 +MipFactor=1.000000 +SlowVideoBuffering=False +StartupFullscreen=True +CurvedSurfaces=False +CaptureMouse=True +LowDetailTextures=False +ScreenFlashes=True +NoLighting=False +DeadZoneXYZ=True +DeadZoneRUV=False +InvertVertical=False +ScaleXYZ=1000.0 +ScaleRUV=2000.0 +MinDesiredFrameRate=30.0 + +[Engine.Player] +ConfiguredInternetSpeed=2600 +ConfiguredLanSpeed=20000 + +[Audio.GenericAudioSubsystem] +UseFilter=True +UseSurround=False +UseStereo=True +UseCDMusic=False +UseDigitalMusic=False +UseSpatial=False +UseReverb=False +Use3dHardware=False +LowSoundQuality=False +ReverseStereo=False +Latency=40 +OutputRate=22050Hz +Channels=16 +MusicVolume=160 +SoundVolume=200 +AmbientFactor=0.7 + +[Galaxy.GalaxyAudioSubsystem] +UseDirectSound=True +UseFilter=True +UseSurround=False +UseStereo=True +UseCDMusic=False +UseDigitalMusic=True +UseSpatial=False +UseReverb=True +Use3dHardware=False +LowSoundQuality=False +ReverseStereo=False +Latency=40 +OutputRate=22050Hz +EffectsChannels=16 +DopplerSpeed=9000.000000 +MusicVolume=160 +SoundVolume=200 +AmbientFactor=0.700000 + +[IpDrv.TcpNetDriver] +AllowDownloads=True +ConnectionTimeout=15.0 +InitialConnectTimeout=120.0 +AckTimeout=1.0 +KeepAliveTime=0.2 +MaxClientRate=20000 +MaxDownloadSize=0 +SimLatency=0 +RelevantTimeout=5.0 +SpawnPrioritySeconds=1.0 +ServerTravelPause=4.0 +NetServerMaxTickRate=20 +LanServerMaxTickRate=35 +DownloadManagers=IpDrv.HTTPDownload +DownloadManagers=Engine.ChannelDownload + +[IpDrv.HTTPDownload] +RedirectToURL=http://uz.ut-files.com/ +ProxyServerHost= +ProxyServerPort=3128 +UseCompression=True + +[Engine.DemoRecDriver] +DemoSpectatorClass=Botpack.CHSpectator +MaxClientRate=25000 +ConnectionTimeout=15.0 +InitialConnectTimeout=500.0 +AckTimeout=1.0 +KeepAliveTime=1.0 +SimLatency=0 +RelevantTimeout=5.0 +SpawnPrioritySeconds=1.0 +ServerTravelPause=4.0 +NetServerMaxTickRate=60 +LanServerMaxTickRate=60 + +[Engine.GameReplicationInfo] +ServerName=Alt Serv Name FULL +ShortName=Alt Serv Name SHORT +AdminName=TestAdminName +AdminEmail=TestAdminName@test.com +Region=0 +MOTDLine1=TestMOTDLine1 +MOTDLine2=TestMOTDLine2 +MOTDLine3=TestMOTDLine3 +MOTDLine4=TestMOTDLine4 + +[IpDrv.TcpipConnection] +SimPacketLoss=0 +SimLatency=0 + +[IpServer.UdpServerQuery] +DoUplink=True +UpdateMinutes=1 +MasterServerAddress=unreal.epicgames.com +MasterServerPort=27900 +Region=0 + +[IpDrv.UdpBeacon] +DoBeacon=True +BeaconTime=0.50 +BeaconTimeout=5.0 +BeaconProduct=ut + +[SoftDrv.SoftwareRenderDevice] +Translucency=True +VolumetricLighting=True +ShinySurfaces=False +Coronas=False +HighDetailActors=False +HighResTextureSmooth=True +LowResTextureSmooth=False +FastTranslucency=True + +[GlideDrv.GlideRenderDevice] +Translucency=True +VolumetricLighting=True +ShinySurfaces=True +Coronas=True +HighDetailActors=True +DetailBias=-1.500000 +RefreshRate=60Hz +DetailTextures=True +FastUglyRefresh=False +ScreenSmoothing=True +Resolution=Default + +[MetalDrv.MetalRenderDevice] +Translucency=True +VolumetricLighting=True +ShinySurfaces=True +Coronas=True +HighDetailActors=True +DetailTextures=False + +[OpenGLDrv.OpenGLRenderDevice] +Translucency=True +VolumetricLighting=False +ShinySurfaces=True +Coronas=True +HighDetailActors=True +DetailTextures=True + +[D3DDrv.D3DRenderDevice] +Translucency=True +VolumetricLighting=False +ShinySurfaces=True +Coronas=True +HighDetailActors=True +UseMipmapping=True +UseTrilinear=False +UseMultitexture=True +UsePageFlipping=True +UsePalettes=True +UseFullscreen=True +UseGammaCorrection=True +DetailTextures=False +Use3dfx=False +UseTripleBuffering=True +UsePrecache=True +Use32BitTextures=False + +[SglDrv.SglRenderDevice] +Translucency=True +VolumetricLighting=False +ShinySurfaces=False +Coronas=True +HighDetailActors=False +ColorDepth=16 +DetailTextures=False +FastUglyRefresh=False +TextureDetailBias=Near +VertexLighting=False + +[Editor.EditorEngine] +UseSound=True +CacheSizeMegs=6 +GridEnabled=True +SnapVertices=True +SnapDistance=10.000000 +GridSize=(X=16.000000,Y=16.000000,Z=16.000000) +RotGridEnabled=True +RotGridSize=(Pitch=1024,Yaw=1024,Roll=1024) +GameCommandLine=-log +FovAngleDegrees=90.000000 +GodMode=True +AutoSave=False +AutoSaveTimeMinutes=5 +AutoSaveIndex=6 +C_WorldBox=(R=0,G=0,B=107,A=0) +C_GroundPlane=(R=0,G=0,B=63,A=0) +C_GroundHighlight=(R=0,G=0,B=127,A=0) +C_BrushWire=(R=255,G=63,B=63,A=0) +C_Pivot=(R=0,G=255,B=0,A=0) +C_Select=(R=0,G=0,B=127,A=0) +C_AddWire=(R=127,G=127,B=255,A=0) +C_SubtractWire=(R=255,G=192,B=63,A=0) +C_GreyWire=(R=163,G=163,B=163,A=0) +C_Invalid=(R=163,G=163,B=163,A=0) +C_ActorWire=(R=127,G=63,B=0,A=0) +C_ActorHiWire=(R=255,G=127,B=0,A=0) +C_White=(R=255,G=255,B=255,A=0) +C_SemiSolidWire=(R=127,G=255,B=0,A=0) +C_NonSolidWire=(R=63,G=192,B=32,A=0) +C_WireGridAxis=(R=119,G=119,B=119,A=0) +C_ActorArrow=(R=163,G=0,B=0,A=0) +C_ScaleBox=(R=151,G=67,B=11,A=0) +C_ScaleBoxHi=(R=223,G=149,B=157,A=0) +C_Mover=(R=255,G=0,B=255,A=0) +C_OrthoBackground=(R=163,G=163,B=163,A=0) +C_Current=(R=0,G=0,B=0,A=0) +C_BrushVertex=(R=0,G=0,B=0,A=0) +C_BrushSnap=(R=0,G=0,B=0,A=0) +C_Black=(R=0,G=0,B=0,A=0) +C_Mask=(R=0,G=0,B=0,A=0) +C_WireBackground=(R=0,G=0,B=0,A=0) +C_ZoneWire=(R=0,G=0,B=0,A=0) +EditPackages=Core +EditPackages=Engine +EditPackages=Editor +EditPackages=UWindow +EditPackages=Fire +EditPackages=IpDrv +EditPackages=UWeb +EditPackages=UBrowser +EditPackages=UnrealShare +EditPackages=UnrealI +EditPackages=UMenu +EditPackages=IpServer +EditPackages=Botpack +EditPackages=UTServerAdmin +EditPackages=UTMenu +EditPackages=UTBrowser + +[UMenu.UnrealConsole] +RootWindow=UMenu.UMenuRootWindow +UWindowKey=IK_Esc +ShowDesktop=True + +[UMenu.UMenuMenuBar] +ShowHelp=True +GameUMenuDefault="UTMenu.UTGameMenu" +MultiplayerUMenuDefault="UTMenu.UTMultiplayerMenu" +OptionsUMenuDefault="UTMenu.UTOptionsMenu" + +[Botpack.ChallengeBotInfo] +Difficulty=1 + +[Botpack.DeathMatchPlus] +bNoviceMode=True +bHardCoreMode=True +bUseTranslocator=False +bCoopWeaponMode=False +bForceRespawn=True +TimeLimit=323 +FragLimit=321 +InitialBots=7 +MinPlayers=11 +bTournament=False + +[Botpack.CTFGame] +bUseTranslocator=True +bCoopWeaponMode=True +GoalTeamScore=3 +bForceRespawn=False +TimeLimit=223 + +[Botpack.Domination] +bDumbDown=True +bUseTranslocator=True +bCoopWeaponMode=True +GoalTeamScore=100 +bForceRespawn=False +TimeLimit=423 + +[Botpack.Assault] +bUseTranslocator=False +bCoopWeaponMode=True +bForceRespawn=True +TimeLimit=123 + +[Botpack.TeamGamePlus] +bBalanceTeams=True +GoalTeamScore=30 +bPlayersBalanceTeams=True +MaxTeamSize=4 + +[Engine.GameInfo] +bLowGore=False +bVeryLowGore=False +MaxSpectators=1 +MaxPlayers=4 +ServerLogName=server.log +bWorldLog=True +bBatchLocal=False +DemoBuild=0 +DemoHasTuts=0 +PlayerViewDelay=1.000000 +PlayerSpeechDelay=0.300000 +PlayerTauntDelay=2.000000 +bLogAdminActions=False +LoginDelaySeconds=0.000000 +MaxLoginAttempts=0 +ActionToTake=DO_Nothing +IPPolicies[0]=ACCEPT,* +IPPolicies[1]= +GamePassword=TestPwd + +[UnrealShare.UnrealGameOptionsMenu] +bCanModifyGore=True + +[UBrowser.UBrowserMainClientWindow] +LANTabName=UBrowserLAN +ServerListNames[0]=UBrowserUT +ServerListNames[1]=UBrowserLAN +ServerListNames[2]=UBrowserPopulated +ServerListNames[3]=UBrowserDeathmatch +ServerListNames[4]=UBrowserTeamGames +ServerListNames[5]=UBrowserCTF +ServerListNames[6]=UBrowserDOM +ServerListNames[7]=UBrowserAS +ServerListNames[8]=UBrowserLMS +ServerListNames[9]=UBrowserAll +ServerListNames[10]=None +ServerListNames[11]=None +ServerListNames[12]=None +ServerListNames[13]=None +ServerListNames[14]=None +ServerListNames[15]=None +ServerListNames[16]=None +ServerListNames[17]=None +ServerListNames[18]=None +ServerListNames[19]=None + +[UBrowserUT] +ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,bCompatibleServersOnly=True + +[UBrowserLAN] +ListFactories[0]=UBrowser.UBrowserLocalFact,BeaconProduct=ut +URLAppend=?LAN +AutoRefreshTime=10 +bNoAutoSort=True + +[UBrowserPopulated] +ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,MinPlayers=1,bCompatibleServersOnly=True + +[UBrowserDeathmatch] +ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=DeathMatchPlus,bCompatibleServersOnly=True + +[UBrowserTeamGames] +ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=TeamGamePlus,bCompatibleServersOnly=True + +[UBrowserCTF] +ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=CTFGame,bCompatibleServersOnly=True + +[UBrowserDOM] +ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=Domination,bCompatibleServersOnly=True + +[UBrowserAS] +ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=Assault,bCompatibleServersOnly=True + +[UBrowserLMS] +ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=LastManStanding,bCompatibleServersOnly=True + +[UBrowserAll] +ListFactories[0]=UBrowser.UBrowserGSpyFact,MasterServerAddress=unreal.epicgames.com,MasterServerTCPPort=28900,Region=0,GameName=ut +ListFactories[1]=UBrowser.UBrowserGSpyFact,MasterServerAddress=master0.gamespy.com,MasterServerTCPPort=28900,Region=0,GameName=ut +bHidden=True +bFallbackFactories=True + +[UTMenu.UTMultiplayerMenu] +OnlineServices[0]=LOCALIZE,MPlayer +OnlineServices[1]=LOCALIZE,Heat +OnlineServices[2]=LOCALIZE,WON + +[UWeb.WebServer] +Applications[0]=UTServerAdmin.UTServerAdmin +ApplicationPaths[0]=/ServerAdmin +Applications[1]=UTServerAdmin.UTImageServer +ApplicationPaths[1]=/images +DefaultApplication=0 +bEnabled=True +ListenPort=9999 +MaxConnections=30 + +[UBrowser.UBrowserHTTPClient] +ProxyServerAddress= +ProxyServerPort= + +[UTServerAdmin.UTServerAdmin] +AdminUsername=TestAdminUser +AdminPassword=TestAdminPwd + diff --git a/test/data/UT99/System/UnrealTournament.ini b/test/data/UT99/System/UnrealTournament.ini new file mode 100644 index 0000000..6b121f7 --- /dev/null +++ b/test/data/UT99/System/UnrealTournament.ini @@ -0,0 +1,520 @@ +[URL] +Protocol=unreal +ProtocolDescription=Unreal Protocol +Name=Player +Map=Index.unr +LocalMap=DM-Codex.unr +Host= +Portal= +MapExt=unr +SaveExt=usa +Port=7777 +Class=Botpack.TMale1 + +[FirstRun] +FirstRun=0 + +[PackageRemap] +UnrealShare=UnrealI + +[Engine.Engine] +GameRenderDevice=SoftDrv.SoftwareRenderDevice +AudioDevice=Galaxy.GalaxyAudioSubsystem +NetworkDevice=IpDrv.TcpNetDriver +DemoRecordingDevice=Engine.DemoRecDriver +Console=UTMenu.UTConsole +Language=int +GameEngine=Engine.GameEngine +EditorEngine=Editor.EditorEngine +WindowedRenderDevice=SoftDrv.SoftwareRenderDevice +RenderDevice=GlideDrv.GlideRenderDevice +DefaultGame=Botpack.DeathMatchPlus +DefaultServerGame=Botpack.DeathMatchPlus +ViewportManager=WinDrv.WindowsClient +Render=Render.Render +Input=Engine.Input +Canvas=Engine.Canvas + +[Core.System] +PurgeCacheDays=30 +SavePath=../Save +CachePath=../Cache +CacheExt=.uxx +Paths=../System/*.u +Paths=../Maps/*.unr +Paths=../Textures/*.utx +Paths=../Sounds/*.uax +Paths=../Music/*.umx +Suppress=DevLoad +Suppress=DevSave +Suppress=DevNetTraffic +Suppress=DevGarbage +Suppress=DevKill +Suppress=DevReplace +Suppress=DevSound +Suppress=DevCompile +Suppress=DevBind +Suppress=DevBsp + +[Engine.GameEngine] +CacheSizeMegs=4 +UseSound=True +ServerActors=IpDrv.UdpBeacon +ServerActors=IpServer.UdpServerQuery +ServerActors=IpServer.UdpServerUplink MasterServerAddress=unreal.epicgames.com MasterServerPort=27900 +ServerActors=IpServer.UdpServerUplink MasterServerAddress=master0.gamespy.com MasterServerPort=27900 +ServerActors=IpServer.UdpServerUplink MasterServerAddress=master.mplayer.com MasterServerPort=27900 +ServerActors=UWeb.WebServer +ServerPackages=SoldierSkins +ServerPackages=CommandoSkins +ServerPackages=FCommandoSkins +ServerPackages=SGirlSkins +ServerPackages=BossSkins +ServerPackages=Botpack + +[WinDrv.WindowsClient] +WindowedViewportX=640 +WindowedViewportY=480 +WindowedColorBits=16 +FullscreenViewportX=640 +FullscreenViewportY=480 +FullscreenColorBits=16 +Brightness=0.500000 +MipFactor=1.000000 +UseDirectDraw=True +UseJoystick=False +CaptureMouse=True +StartupFullscreen=True +CurvedSurfaces=False +LowDetailTextures=False +ScreenFlashes=True +NoLighting=False +SlowVideoBuffering=True +DeadZoneXYZ=True +DeadZoneRUV=False +InvertVertical=False +ScaleXYZ=1000.0 +ScaleRUV=2000.0 +MinDesiredFrameRate=30.0 +Decals=True +NoDynamicLights=False + +[XDrv.XClient] +WindowedViewportX=640 +WindowedViewportY=480 +WindowedColorBits=16 +FullscreenViewportX=640 +FullscreenViewportY=480 +FullscreenColorBits=16 +Brightness=0.500000 +MipFactor=1.000000 +SlowVideoBuffering=False +StartupFullscreen=True +CurvedSurfaces=False +CaptureMouse=True +LowDetailTextures=False +ScreenFlashes=True +NoLighting=False +DeadZoneXYZ=True +DeadZoneRUV=False +InvertVertical=False +ScaleXYZ=1000.0 +ScaleRUV=2000.0 +MinDesiredFrameRate=30.0 + +[Engine.Player] +ConfiguredInternetSpeed=2600 +ConfiguredLanSpeed=20000 + +[Audio.GenericAudioSubsystem] +UseFilter=True +UseSurround=False +UseStereo=True +UseCDMusic=False +UseDigitalMusic=False +UseSpatial=False +UseReverb=False +Use3dHardware=False +LowSoundQuality=False +ReverseStereo=False +Latency=40 +OutputRate=22050Hz +Channels=16 +MusicVolume=160 +SoundVolume=200 +AmbientFactor=0.7 + +[Galaxy.GalaxyAudioSubsystem] +UseDirectSound=True +UseFilter=True +UseSurround=False +UseStereo=True +UseCDMusic=False +UseDigitalMusic=True +UseSpatial=False +UseReverb=True +Use3dHardware=False +LowSoundQuality=False +ReverseStereo=False +Latency=40 +OutputRate=22050Hz +EffectsChannels=16 +DopplerSpeed=9000.000000 +MusicVolume=160 +SoundVolume=200 +AmbientFactor=0.700000 + +[IpDrv.TcpNetDriver] +AllowDownloads=True +ConnectionTimeout=15.0 +InitialConnectTimeout=120.0 +AckTimeout=1.0 +KeepAliveTime=0.2 +MaxClientRate=20000 +MaxDownloadSize=0 +SimLatency=0 +RelevantTimeout=5.0 +SpawnPrioritySeconds=1.0 +ServerTravelPause=4.0 +NetServerMaxTickRate=20 +LanServerMaxTickRate=35 +DownloadManagers=IpDrv.HTTPDownload +DownloadManagers=Engine.ChannelDownload + +[IpDrv.HTTPDownload] +RedirectToURL=http://uz.ut-files.com/ +ProxyServerHost= +ProxyServerPort=3128 +UseCompression=True + +[Engine.DemoRecDriver] +DemoSpectatorClass=Botpack.CHSpectator +MaxClientRate=25000 +ConnectionTimeout=15.0 +InitialConnectTimeout=500.0 +AckTimeout=1.0 +KeepAliveTime=1.0 +SimLatency=0 +RelevantTimeout=5.0 +SpawnPrioritySeconds=1.0 +ServerTravelPause=4.0 +NetServerMaxTickRate=60 +LanServerMaxTickRate=60 + +[Engine.GameReplicationInfo] +ServerName=Test Server Name FULL +ShortName=Test Server Name SHORT +AdminName=TestAdminName +AdminEmail=TestAdminName@test.com +Region=0 +MOTDLine1=TestMOTDLine1 +MOTDLine2=TestMOTDLine2 +MOTDLine3=TestMOTDLine3 +MOTDLine4=TestMOTDLine4 + +[IpDrv.TcpipConnection] +SimPacketLoss=0 +SimLatency=0 + +[IpServer.UdpServerQuery] +DoUplink=True +UpdateMinutes=1 +MasterServerAddress=unreal.epicgames.com +MasterServerPort=27900 +Region=0 + +[IpDrv.UdpBeacon] +DoBeacon=True +BeaconTime=0.50 +BeaconTimeout=5.0 +BeaconProduct=ut + +[SoftDrv.SoftwareRenderDevice] +Translucency=True +VolumetricLighting=True +ShinySurfaces=False +Coronas=False +HighDetailActors=False +HighResTextureSmooth=True +LowResTextureSmooth=False +FastTranslucency=True + +[GlideDrv.GlideRenderDevice] +Translucency=True +VolumetricLighting=True +ShinySurfaces=True +Coronas=True +HighDetailActors=True +DetailBias=-1.500000 +RefreshRate=60Hz +DetailTextures=True +FastUglyRefresh=False +ScreenSmoothing=True +Resolution=Default + +[MetalDrv.MetalRenderDevice] +Translucency=True +VolumetricLighting=True +ShinySurfaces=True +Coronas=True +HighDetailActors=True +DetailTextures=False + +[OpenGLDrv.OpenGLRenderDevice] +Translucency=True +VolumetricLighting=False +ShinySurfaces=True +Coronas=True +HighDetailActors=True +DetailTextures=True + +[D3DDrv.D3DRenderDevice] +Translucency=True +VolumetricLighting=False +ShinySurfaces=True +Coronas=True +HighDetailActors=True +UseMipmapping=True +UseTrilinear=False +UseMultitexture=True +UsePageFlipping=True +UsePalettes=True +UseFullscreen=True +UseGammaCorrection=True +DetailTextures=False +Use3dfx=False +UseTripleBuffering=True +UsePrecache=True +Use32BitTextures=False + +[SglDrv.SglRenderDevice] +Translucency=True +VolumetricLighting=False +ShinySurfaces=False +Coronas=True +HighDetailActors=False +ColorDepth=16 +DetailTextures=False +FastUglyRefresh=False +TextureDetailBias=Near +VertexLighting=False + +[Editor.EditorEngine] +UseSound=True +CacheSizeMegs=6 +GridEnabled=True +SnapVertices=True +SnapDistance=10.000000 +GridSize=(X=16.000000,Y=16.000000,Z=16.000000) +RotGridEnabled=True +RotGridSize=(Pitch=1024,Yaw=1024,Roll=1024) +GameCommandLine=-log +FovAngleDegrees=90.000000 +GodMode=True +AutoSave=False +AutoSaveTimeMinutes=5 +AutoSaveIndex=6 +C_WorldBox=(R=0,G=0,B=107,A=0) +C_GroundPlane=(R=0,G=0,B=63,A=0) +C_GroundHighlight=(R=0,G=0,B=127,A=0) +C_BrushWire=(R=255,G=63,B=63,A=0) +C_Pivot=(R=0,G=255,B=0,A=0) +C_Select=(R=0,G=0,B=127,A=0) +C_AddWire=(R=127,G=127,B=255,A=0) +C_SubtractWire=(R=255,G=192,B=63,A=0) +C_GreyWire=(R=163,G=163,B=163,A=0) +C_Invalid=(R=163,G=163,B=163,A=0) +C_ActorWire=(R=127,G=63,B=0,A=0) +C_ActorHiWire=(R=255,G=127,B=0,A=0) +C_White=(R=255,G=255,B=255,A=0) +C_SemiSolidWire=(R=127,G=255,B=0,A=0) +C_NonSolidWire=(R=63,G=192,B=32,A=0) +C_WireGridAxis=(R=119,G=119,B=119,A=0) +C_ActorArrow=(R=163,G=0,B=0,A=0) +C_ScaleBox=(R=151,G=67,B=11,A=0) +C_ScaleBoxHi=(R=223,G=149,B=157,A=0) +C_Mover=(R=255,G=0,B=255,A=0) +C_OrthoBackground=(R=163,G=163,B=163,A=0) +C_Current=(R=0,G=0,B=0,A=0) +C_BrushVertex=(R=0,G=0,B=0,A=0) +C_BrushSnap=(R=0,G=0,B=0,A=0) +C_Black=(R=0,G=0,B=0,A=0) +C_Mask=(R=0,G=0,B=0,A=0) +C_WireBackground=(R=0,G=0,B=0,A=0) +C_ZoneWire=(R=0,G=0,B=0,A=0) +EditPackages=Core +EditPackages=Engine +EditPackages=Editor +EditPackages=UWindow +EditPackages=Fire +EditPackages=IpDrv +EditPackages=UWeb +EditPackages=UBrowser +EditPackages=UnrealShare +EditPackages=UnrealI +EditPackages=UMenu +EditPackages=IpServer +EditPackages=Botpack +EditPackages=UTServerAdmin +EditPackages=UTMenu +EditPackages=UTBrowser + +[UMenu.UnrealConsole] +RootWindow=UMenu.UMenuRootWindow +UWindowKey=IK_Esc +ShowDesktop=True + +[UMenu.UMenuMenuBar] +ShowHelp=True +GameUMenuDefault="UTMenu.UTGameMenu" +MultiplayerUMenuDefault="UTMenu.UTMultiplayerMenu" +OptionsUMenuDefault="UTMenu.UTOptionsMenu" + +[Botpack.ChallengeBotInfo] +Difficulty=1 + +[Botpack.DeathMatchPlus] +bNoviceMode=True +bHardCoreMode=True +bUseTranslocator=False +bCoopWeaponMode=False +bForceRespawn=True +TimeLimit=323 +FragLimit=321 +InitialBots=7 +MinPlayers=11 +bTournament=False + +[Botpack.CTFGame] +bUseTranslocator=True +bCoopWeaponMode=True +GoalTeamScore=3 +bForceRespawn=False +TimeLimit=223 + +[Botpack.Domination] +bDumbDown=True +bUseTranslocator=True +bCoopWeaponMode=True +GoalTeamScore=100 +bForceRespawn=False +TimeLimit=423 + +[Botpack.Assault] +bUseTranslocator=False +bCoopWeaponMode=True +bForceRespawn=True +TimeLimit=123 + +[Botpack.TeamGamePlus] +bBalanceTeams=True +GoalTeamScore=30 +bPlayersBalanceTeams=True +MaxTeamSize=4 + +[Engine.GameInfo] +bLowGore=False +bVeryLowGore=False +MaxSpectators=1 +MaxPlayers=4 +ServerLogName=server.log +bWorldLog=True +bBatchLocal=False +DemoBuild=0 +DemoHasTuts=0 +PlayerViewDelay=1.000000 +PlayerSpeechDelay=0.300000 +PlayerTauntDelay=2.000000 +bLogAdminActions=False +LoginDelaySeconds=0.000000 +MaxLoginAttempts=0 +ActionToTake=DO_Nothing +IPPolicies[0]=ACCEPT,* +IPPolicies[1]= +GamePassword=TestPwd + +[UnrealShare.UnrealGameOptionsMenu] +bCanModifyGore=True + +[UBrowser.UBrowserMainClientWindow] +LANTabName=UBrowserLAN +ServerListNames[0]=UBrowserUT +ServerListNames[1]=UBrowserLAN +ServerListNames[2]=UBrowserPopulated +ServerListNames[3]=UBrowserDeathmatch +ServerListNames[4]=UBrowserTeamGames +ServerListNames[5]=UBrowserCTF +ServerListNames[6]=UBrowserDOM +ServerListNames[7]=UBrowserAS +ServerListNames[8]=UBrowserLMS +ServerListNames[9]=UBrowserAll +ServerListNames[10]=None +ServerListNames[11]=None +ServerListNames[12]=None +ServerListNames[13]=None +ServerListNames[14]=None +ServerListNames[15]=None +ServerListNames[16]=None +ServerListNames[17]=None +ServerListNames[18]=None +ServerListNames[19]=None + +[UBrowserUT] +ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,bCompatibleServersOnly=True + +[UBrowserLAN] +ListFactories[0]=UBrowser.UBrowserLocalFact,BeaconProduct=ut +URLAppend=?LAN +AutoRefreshTime=10 +bNoAutoSort=True + +[UBrowserPopulated] +ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,MinPlayers=1,bCompatibleServersOnly=True + +[UBrowserDeathmatch] +ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=DeathMatchPlus,bCompatibleServersOnly=True + +[UBrowserTeamGames] +ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=TeamGamePlus,bCompatibleServersOnly=True + +[UBrowserCTF] +ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=CTFGame,bCompatibleServersOnly=True + +[UBrowserDOM] +ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=Domination,bCompatibleServersOnly=True + +[UBrowserAS] +ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=Assault,bCompatibleServersOnly=True + +[UBrowserLMS] +ListFactories[0]=UBrowser.UBrowserSubsetFact,SupersetTag=UBrowserAll,GameType=LastManStanding,bCompatibleServersOnly=True + +[UBrowserAll] +ListFactories[0]=UBrowser.UBrowserGSpyFact,MasterServerAddress=unreal.epicgames.com,MasterServerTCPPort=28900,Region=0,GameName=ut +ListFactories[1]=UBrowser.UBrowserGSpyFact,MasterServerAddress=master0.gamespy.com,MasterServerTCPPort=28900,Region=0,GameName=ut +bHidden=True +bFallbackFactories=True + +[UTMenu.UTMultiplayerMenu] +OnlineServices[0]=LOCALIZE,MPlayer +OnlineServices[1]=LOCALIZE,Heat +OnlineServices[2]=LOCALIZE,WON + +[UWeb.WebServer] +Applications[0]=UTServerAdmin.UTServerAdmin +ApplicationPaths[0]=/ServerAdmin +Applications[1]=UTServerAdmin.UTImageServer +ApplicationPaths[1]=/images +DefaultApplication=0 +bEnabled=True +ListenPort=9999 +MaxConnections=30 + +[UBrowser.UBrowserHTTPClient] +ProxyServerAddress= +ProxyServerPort= + +[UTServerAdmin.UTServerAdmin] +AdminUsername=TestAdminUser +AdminPassword=TestAdminPwd + diff --git a/test/test_gen.py b/test/test_gen.py new file mode 100644 index 0000000..c6e088c --- /dev/null +++ b/test/test_gen.py @@ -0,0 +1,33 @@ +# pygamecfg (c) by chacha +# +# pygamecfg 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 . + +import unittest +from io import StringIO +from contextlib import redirect_stdout, redirect_stderr + +print(__name__) +print(__package__) + +from src import pygamecfg +from src.pygamecfg.__main__ import fct_main + + +class Testtest_gen(unittest.TestCase): + def test_version(self): + self.assertNotEqual(pygamecfg.__version__, "?.?.?") + + def test_normal_help(self): + with self.assertRaises(SystemExit) as cm: + fct_main(["-h"]) + + def test_defect_nogame(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + with self.assertRaises(SystemExit) as cm: + fct_main(["GetOption", "test"]) + self.assertIn("pygamecfg: error: the following arguments are required: -g/--game", capted_stderr.getvalue()) + self.assertIn("", capted_stdout.getvalue()) diff --git a/test/test_ut99.py b/test/test_ut99.py index 830fb1a..b461be9 100644 --- a/test/test_ut99.py +++ b/test/test_ut99.py @@ -7,6 +7,7 @@ # work. If not, see . import unittest +from os import linesep from io import StringIO from contextlib import redirect_stdout, redirect_stderr @@ -14,8 +15,298 @@ print(__name__) print(__package__) from src import pygamecfg +from src.pygamecfg.__main__ import fct_main -class Testtest_module(unittest.TestCase): - def test_version(self): - self.assertNotEqual(pygamecfg.__version__, "?.?.?") +class Testtest_ut99(unittest.TestCase): + def test_normal_ServerPackages(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "ServerPackages"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual( + "['SoldierSkins', 'CommandoSkins', 'FCommandoSkins', 'SGirlSkins', 'BossSkins', 'Botpack']\n", capted_stdout.getvalue() + ) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_ServerActors(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "ServerActors"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual( + "['IpDrv.UdpBeacon', 'IpServer.UdpServerQuery', 'IpServer.UdpServerUplink MasterServerAddress=unreal.epicgames.com MasterServerPort=27900', 'IpServer.UdpServerUplink MasterServerAddress=master0.gamespy.com MasterServerPort=27900', 'IpServer.UdpServerUplink MasterServerAddress=master.mplayer.com MasterServerPort=27900', 'UWeb.WebServer']\n", + capted_stdout.getvalue(), + ) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_Port(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "Port"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("7777\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_Map(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "Map"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("Index.unr\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_GameType(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "GameType"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("Botpack.DeathMatchPlus\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_HostName(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "HostName"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("Test Server Name FULL\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_MOTD(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MOTD"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("TestMOTDLine1\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_MOTD2(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MOTD2"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("TestMOTDLine2\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_MOTD3(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MOTD3"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("TestMOTDLine3\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_MOTD4(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MOTD4"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("TestMOTDLine4\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_AdminEmail(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "AdminEmail"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("TestAdminName@test.com\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_AdminName(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "AdminName"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("TestAdminName\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_HTTPDownloadServer(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "HTTPDownloadServer"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("http://uz.ut-files.com/\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_MaxClientRate(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MaxClientRate"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("20000\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_NetServerMaxTickRate(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "NetServerMaxTickRate"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("20\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_LanServerMaxTickRate(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "LanServerMaxTickRate"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("35\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_AdminPassword(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "AdminPassword"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("TestAdminPwd\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_GamePassword(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "GamePassword"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("TestPwd\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_MaxPlayers(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MaxPlayers"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("4\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_MaxSpectators(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MaxSpectators"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("1\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_AS_TimeLimit(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "AS_TimeLimit"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("123\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_DOM_TimeLimit(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "DOM_TimeLimit"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("423\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_CTF_TimeLimit(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "CTF_TimeLimit"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("223\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_DM_TimeLimit(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "DM_TimeLimit"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("323\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_GoalTeamScore(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "GoalTeamScore"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("30\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_MaxTeamSize(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MaxTeamSize"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("4\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_DM_FragLimit(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "DM_FragLimit"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("321\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_ServerLogName(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "ServerLogName"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("server.log\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_WebServer(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "WebServer"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("9999\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_TournamentMode(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "TournamentMode"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("False\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_InitialBots(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "InitialBots"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("7\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_MinPlayers(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "MinPlayers"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("11\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_AS_UseTranslocator(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "AS_UseTranslocator"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("True\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_CTF_UseTranslocator(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "CTF_UseTranslocator"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("True\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_DM_UseTranslocator(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "DM_UseTranslocator"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("False\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_DOM_UseTranslocator(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "DOM_UseTranslocator"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("True\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_CTF_ForceRespawn(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "CTF_ForceRespawn"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("False\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_DM_ForceRespawn(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "DM_ForceRespawn"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("True\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_DOM_ForceRespawn(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "DOM_ForceRespawn"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("False\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_AS_ForceRespawn(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "AS_ForceRespawn"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("True\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) + + def test_normal_customcfgfile_HostName(self): + with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: + fct_main(["-g", "ut99", "-b", "test/data/UT99", "-c", "System/UT99.ini", "GetOption", "HostName"]) + # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep + self.assertEqual("Alt Serv Name FULL\n", capted_stdout.getvalue()) + self.assertEqual("", capted_stderr.getvalue()) -- 2.47.3 From 246d4c952902f3ead946d3f70059abbc0f5c1f85 Mon Sep 17 00:00:00 2001 From: cclecle Date: Sat, 23 Sep 2023 20:03:24 +0100 Subject: [PATCH 04/13] fix: fix most pylint / type hint warnings/errors --- pyproject.toml | 3 +- src/pygamecfg/__init__.py | 4 +- src/pygamecfg/__main__.py | 90 +++++++++++++++------ src/pygamecfg/core.py | 68 ++++++++++++---- src/pygamecfg/{cod4.py => game_cod4.py} | 12 ++- src/pygamecfg/{ut2k4.py => game_ut2k4.py} | 94 +++++++++++---------- src/pygamecfg/{ut99.py => game_ut99.py} | 99 +++++++++++------------ src/pygamecfg/tool_ini.py | 33 ++++++++ test/test_ut99.py | 5 ++ 9 files changed, 264 insertions(+), 144 deletions(-) rename src/pygamecfg/{cod4.py => game_cod4.py} (85%) rename src/pygamecfg/{ut2k4.py => game_ut2k4.py} (88%) rename src/pygamecfg/{ut99.py => game_ut99.py} (85%) create mode 100644 src/pygamecfg/tool_ini.py diff --git a/pyproject.toml b/pyproject.toml index 87d9661..1c862ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,8 @@ classifiers = [ dependencies = [ 'importlib-metadata; python_version<"3.9"', 'packaging', - 'pysimpleini>=0.3.1' + 'pysimpleini>=0.3.1', + 'typed-argument-parser==1.x' ] dynamic = ["version"] diff --git a/src/pygamecfg/__init__.py b/src/pygamecfg/__init__.py index 80ca5b9..c6fcc12 100644 --- a/src/pygamecfg/__init__.py +++ b/src/pygamecfg/__init__.py @@ -15,7 +15,9 @@ import warnings from .core import GameOptions_Factory -from . import ut99, ut2k4, cod4 +from . import game_cod4 +from . import game_ut99 +from . import game_ut2k4 try: # pragma: no cover __version__ = version("pygamecfg") diff --git a/src/pygamecfg/__main__.py b/src/pygamecfg/__main__.py index 3478d88..5678e48 100644 --- a/src/pygamecfg/__main__.py +++ b/src/pygamecfg/__main__.py @@ -7,42 +7,80 @@ # # You should have received a copy of the license along with this # work. If not, see . - """CLI interface module""" from __future__ import annotations +from typing import Literal, cast, Union import sys -from argparse import ArgumentParser +from tap import Tap from . import __Summuary__, __Name__ from . import GameOptions_Factory -def fct_main(args: list[str]) -> int: - parser = ArgumentParser(prog=__Name__, description=__Summuary__) - parser.add_argument("-v", "--verbosity", action="count", default=0, help="increase output verbosity") +class pygamecfg_args_SetOption(Tap): + """SetOption CLI arg subparser""" - parser.add_argument("-b", "--basegamedir", help="set the game base dir", default="./") - parser.add_argument("-c", "--configfile", help="set the default config file", default="") - parser.add_argument("-g", "--game", help="the target game", choices=["ut99", "cod4", "ut2k4"], required=True) - subparsers = parser.add_subparsers(dest="command", help="command type", required=True) + option: str + value: str = "" - SetOption_subparser = subparsers.add_parser("SetOption", help="Set/Add a game config file option value (may need reboot)") - SetOption_subparser.add_argument("option") - SetOption_subparser.add_argument("value", nargs="?") + def configure(self): + self.add_argument("option") + self.add_argument("value") - RemOption_subparser = subparsers.add_parser("RemOption", help="Remove a game config file option, w or w/o value (may need reboot)") - RemOption_subparser.add_argument("option") - RemOption_subparser.add_argument("value", nargs="?") - GetOption_subparser = subparsers.add_parser("GetOption", help="Get a game config file option value") - GetOption_subparser.add_argument("option") +class pygamecfg_args_RemOption(Tap): + """RemOption CLI arg subparser""" - args = parser.parse_args(args) + option: str + value: str = "" + + def configure(self) -> None: + self.add_argument("option") + self.add_argument("value") + + +class pygamecfg_args_GetOption(Tap): + """GetOption CLI arg subparser""" + + option: str + + def configure(self) -> None: + self.add_argument("option") + + +class pygamecfg_args(Tap): + """Main CLI arg parser""" + + verbosity: int = 0 + basegamedir: str = "./" + configfile: str = "" + game: Literal["ut99", "cod4", "ut2k4"] + + def configure(self) -> None: + self.add_argument("-v", "--verbosity", action="count", help="increase output verbosity") + self.add_argument("-b", "--basegamedir", help="set the game base dir") + self.add_argument("-c", "--configfile", help="set the default config file") + self.add_argument("-g", "--game", help="the target game") + self.add_subparsers(dest="command", help="command type", required=True) + self.add_subparser("SetOption", pygamecfg_args_SetOption, help="Set/Add a game config file option value") + self.add_subparser("RemOption", pygamecfg_args_RemOption, help="Remove a game config file option, w or w/o value") + self.add_subparser("GetOption", pygamecfg_args_GetOption, help="Get a game config file option value") + + def process_args(self) -> None: + """dynamically add self.command to avoid conflict with Tap/argparse while keep pylint happy""" + self.command: Union[str, None] = cast(Union[str, None], self.command) # pylint: disable=attribute-defined-outside-init + + +def fct_main(i_args: list[str]): + """CLI main function""" + parser = pygamecfg_args(prog=__Name__, description=__Summuary__) + + args: pygamecfg_args = parser.parse_args(i_args) if args.verbosity: - print("Using base game dir: {0}".format(args.basegamedir)) - print("Using config file: {0}".format(args.configfile)) + print(f"Using base game dir: {args.basegamedir}") + print(f"Using config file: {args.configfile}") if args.configfile == "": if args.game == "ut99": @@ -51,14 +89,18 @@ def fct_main(args: list[str]) -> int: args.configfile = "./System/UT2004.ini" elif args.game == "cod4": args.configfile = "./main/server.cfg" - _GameOptions = GameOptions_Factory(args.game, args.basegamedir, args.configfile) + GameOptions = GameOptions_Factory(args.game, args.basegamedir, args.configfile) if args.command == "SetOption": - _GameOptions.set(args.option, args.value) + GameOptions.set( + cast(pygamecfg_args_SetOption, args).option, cast(pygamecfg_args_SetOption, args).value # pylint: disable=no-member + ) elif args.command == "RemOption": - _GameOptions.rem(args.option, args.value) + GameOptions.rem( + cast(pygamecfg_args_RemOption, args).option, cast(pygamecfg_args_RemOption, args).value # pylint: disable=no-member + ) elif args.command == "GetOption": - res = _GameOptions.get(args.option) + res = GameOptions.get(cast(pygamecfg_args_GetOption, args).option) # pylint: disable=no-member print(res) else: raise RuntimeError("Invalid argument") diff --git a/src/pygamecfg/core.py b/src/pygamecfg/core.py index b2a1f44..f0ae580 100644 --- a/src/pygamecfg/core.py +++ b/src/pygamecfg/core.py @@ -1,3 +1,6 @@ +""" Core file of pygamecfg +contain generic management code for GameOption +""" from __future__ import annotations from typing import Union @@ -6,6 +9,8 @@ from enum import Enum class OptionType(Enum): + """Supported option data type""" + OT_INVALID = 0 OT_STRING = 1 OT_INTEGER = 2 @@ -14,6 +19,8 @@ class OptionType(Enum): class GameOption(metaclass=ABCMeta): + """Game option base type""" + szGameType: str = "" szOptionName: str = "" TValueType: OptionType = OptionType.OT_INVALID @@ -21,12 +28,24 @@ class GameOption(metaclass=ABCMeta): szHelp: str = "" szFormatedValue: str = "" - @abstractmethod def __init__(self, GameRootDir: str, ConfigFileRelPath: Union[None, str] = None): self.GameRootDir = GameRootDir self.ConfigFileRelPath = ConfigFileRelPath + def __enter__(self): + """contextlib enter hook""" + return self + + def __exit__(self, exception_type, exception_value, exception_traceback): + """contextlib exit hook""" + self.close() + + def close(self): + """user-define close() function (for subclassing)""" + def format(self, value: Union[int, str, float]) -> None: + """standard function to format options before writing it to file (overloadable)""" + if self.TValueType == OptionType.OT_STRING: self.szFormatedValue = str(value) elif self.TValueType == OptionType.OT_INTEGER: @@ -35,63 +54,80 @@ class GameOption(metaclass=ABCMeta): try: intval = int(value) self.szFormatedValue = str(bool(intval)) - except: + except ValueError: self.szFormatedValue = str(True) if str(value).lower() == "true" else str(False) elif self.TValueType == OptionType.OT_FLOAT: self.szFormatedValue = str(float(value)) else: raise RuntimeError("Invalid Option TValueType") - print("setting option <{0}> to: {1}".format(self.szOptionName, self.szFormatedValue)) + print(f"setting option <{self.szOptionName}> to: {self.szFormatedValue}") @abstractmethod def set(self, value: str) -> None: + """generic set function""" raise NotImplementedError("method not implemented") @abstractmethod def rem(self, value: Union[None, str]) -> None: + """generic rem function""" raise NotImplementedError("method not implemented") @abstractmethod def get(self) -> Union[None, str]: + """generic get function""" raise NotImplementedError("method not implemented") class GameOptions_Factory: - ar_Options_cls = [] + """factory that manage game options based on Game and the option itself""" - def __init__(self, szGameType: str, GameRootDir: str, ConfigFileRelPath: Union[None, str] = None) -> None: + ar_Options_cls: list[type[GameOption]] = [] + ar_Options_cls_filtered: list[type[GameOption]] = [] + GameRootDir: str = "./" + ConfigFileRelPath: str = "" + + def __init__(self, szGameType: str, GameRootDir: str, ConfigFileRelPath: str) -> None: self.szGameType = szGameType - self.ar_Options = [] + self.GameRootDir = GameRootDir + self.ConfigFileRelPath = ConfigFileRelPath for Options_cls in GameOptions_Factory.ar_Options_cls: if Options_cls.szGameType == szGameType: - self.ar_Options.append(Options_cls(GameRootDir, ConfigFileRelPath)) + self.ar_Options_cls_filtered.append(Options_cls) @classmethod - def GameOptionRegister(cls, Option: GameOption) -> None: - cls.ar_Options_cls.append(Option) + def GameOptionRegister(cls, Option: type[GameOption]) -> None: + """interface option used by decorator to register option implementation classes""" + GameOptions_Factory.ar_Options_cls.append(Option) def set(self, OptionName: str, value: str) -> None: - for _option in GameOptions_Factory.ar_Options: + """generic set function (API call)""" + for _option in GameOptions_Factory.ar_Options_cls_filtered: if _option.szOptionName == OptionName: - _option.set(value) + with _option(self.GameRootDir, self.ConfigFileRelPath) as _optionInst: + _optionInst.set(value) return raise RuntimeError("Option not found") def rem(self, OptionName: str, value: Union[None, str]) -> None: - for _option in self.ar_Options: + """generic rem function (API call)""" + for _option in self.ar_Options_cls_filtered: if _option.szOptionName == OptionName: - _option.rem(value) + with _option(self.GameRootDir, self.ConfigFileRelPath) as _optionInst: + _optionInst.rem(value) return raise RuntimeError("Option not found") def get(self, OptionName: str) -> Union[None, str]: - for _option in self.ar_Options: + """generic get function (API call)""" + for _option in self.ar_Options_cls_filtered: if _option.szOptionName == OptionName: - return _option.get() + with _option(self.GameRootDir, self.ConfigFileRelPath) as _optionInst: + return _optionInst.get() raise RuntimeError("Option not found") -def GameOptions_Factory_Register(cls): +def GameOptions_Factory_Register(cls: type[GameOption]): + """decorator to register game option concrete implementation""" GameOptions_Factory.GameOptionRegister(cls) return cls diff --git a/src/pygamecfg/cod4.py b/src/pygamecfg/game_cod4.py similarity index 85% rename from src/pygamecfg/cod4.py rename to src/pygamecfg/game_cod4.py index 447b88c..e267071 100644 --- a/src/pygamecfg/cod4.py +++ b/src/pygamecfg/game_cod4.py @@ -1,3 +1,4 @@ +# pylint: disable=duplicate-code,missing-class-docstring,missing-module-docstring,missing-function-docstring from __future__ import annotations from typing import Union @@ -19,7 +20,10 @@ class GameOption_COD4(GameOption): def __init__(self, GameRootDir: str, ConfigFileRelPath: str) -> None: super().__init__(GameRootDir, ConfigFileRelPath) self.mainConfigFilePath = join(GameRootDir, ConfigFileRelPath) - self.cfgfile = open(self.mainConfigFilePath, "r") + self.cfgfile = open(self.mainConfigFilePath, "r", encoding="utf8") # pylint: disable=consider-using-with + + def close(self): + self.cfgfile.close() def set(self, value: str) -> None: if not self.szOptionName: @@ -31,7 +35,7 @@ class GameOption_COD4(GameOption): value = self.szPrefix + " " + self.szKeyName + " " + value for line in self.cfgfile.readlines(): - if re.search(r"\s+" + self.szPrefix + r"\s+"): + if re.search(r"\s+" + self.szPrefix + r"\s+", line): print(f"found: {line}") def rem(self, value: Union[str, None] = None) -> None: @@ -54,9 +58,9 @@ class GameOption_COD4(GameOption): raise RuntimeError("Option not found in file") self.cfgfile.close() - with open(self.mainConfigFilePath, "w") as ofile: + with open(self.mainConfigFilePath, "w", encoding="utf8") as ofile: ofile.write(newFile) - self.cfgfile = open(self.mainConfigFilePath, "r") + self.cfgfile = open(self.mainConfigFilePath, "r", encoding="utf8") # pylint: disable=consider-using-with def get(self) -> Union[None, str]: if not self.szOptionName: diff --git a/src/pygamecfg/ut2k4.py b/src/pygamecfg/game_ut2k4.py similarity index 88% rename from src/pygamecfg/ut2k4.py rename to src/pygamecfg/game_ut2k4.py index 6ac772c..f041511 100644 --- a/src/pygamecfg/ut2k4.py +++ b/src/pygamecfg/game_ut2k4.py @@ -1,37 +1,25 @@ +# pylint: disable=duplicate-code,missing-class-docstring,missing-module-docstring,missing-function-docstring from __future__ import annotations from typing import Union - -from pysimpleini import PySimpleINI, KeyNotFoundError, SectionNotFoundError, Section, Key from os.path import join +from pathlib import Path + +from pysimpleini import KeyNotFoundError from .core import GameOptions_Factory_Register, GameOption, OptionType +from .tool_ini import PySimpleINI_GroupKeysInSection -class ChaChaSimpleINI_UT2k4(PySimpleINI): - def GroupKeysInSection(self, szSectionName: str, szKeyName: str) -> None: - try: - _section = self.getSection(szSectionName) - if type(_section) is Section: - ar_ServerPackages = _section.getKey(szKeyName) - if isinstance(ar_ServerPackages, Key): - ar_ServerPackages = [ar_ServerPackages] - else: # array - pass - for ServerPackages in ar_ServerPackages: - _section.delKey(ServerPackages.name, None, ServerPackages.value) - for ServerPackages in ar_ServerPackages: - _section.setAddKeyValue(ServerPackages.name, ServerPackages.value, True) - except SectionNotFoundError: - pass - - def writeFile(self, bBeautify: bool = False) -> None: - self.GroupKeysInSection("Engine.GameEngine", "ServerPackages") - self.GroupKeysInSection("Engine.GameEngine", "ServerActors") - self.GroupKeysInSection("Core.System", "Suppress") - self.GroupKeysInSection("Core.System", "Paths") - self.GroupKeysInSection("Editor.EditorEngine", "EditPackages") - self.GroupKeysInSection("Editor.EditorEngine", "CutdownPackages") - super().writeFile(bBeautify) +class ChaChaSimpleINI_UT2k4(PySimpleINI_GroupKeysInSection): + GroupRules = [ + ("Engine.GameEngine", "ServerPackages"), + ("Engine.GameEngine", "ServerActors"), + ("Core.System", "Suppress"), + ("Core.System", "Paths"), + ("Core.System", "Paths"), + ("Editor.EditorEngine", "EditPackages"), + ("Editor.EditorEngine", "CutdownPackages"), + ] class GameOption_UT2k4(GameOption): @@ -44,30 +32,40 @@ class GameOption_UT2k4(GameOption): bForceAdd: bool = False bRemovable: bool = False - def __init__(self, GameRootDir: str, ConfigFileRelPath: Union[None, str]) -> None: + cachedFile: Union[None, ChaChaSimpleINI_UT2k4] = None + cachedFilePath: Union[None, Path] = None + + @classmethod + def openFile(cls, filepath: Path) -> ChaChaSimpleINI_UT2k4: + if (not GameOption_UT2k4.cachedFile) or (filepath != GameOption_UT2k4.cachedFilePath): + GameOption_UT2k4.cachedFilePath = filepath + GameOption_UT2k4.cachedFile = ChaChaSimpleINI_UT2k4(filepath) + return GameOption_UT2k4.cachedFile + + def __init__(self, GameRootDir: str, ConfigFileRelPath: str) -> None: super().__init__(GameRootDir, ConfigFileRelPath) - self.mainConfigFilePath = join(GameRootDir, ConfigFileRelPath) + self.mainConfigFilePath: Path = Path(join(GameRootDir, ConfigFileRelPath)) + self.inifile = ChaChaSimpleINI_UT2k4(self.mainConfigFilePath) def set(self, value: str) -> None: if not self.szOptionName: raise RuntimeError("szOptionName is not set") self.format(value) - self.inifile = ChaChaSimpleINI_UT2k4(self.mainConfigFilePath) - self.inifile.setAddKeyValue(self.szSectionName, self.szKeyName, self.szFormatedValue, self.bForceAdd) - self.inifile.writeFile() + self.inifile.setaddkeyvalue(self.szSectionName, self.szKeyName, self.szFormatedValue, self.bForceAdd) + self.inifile.writefile() def rem(self, value: Union[None, str]) -> None: if not self.szOptionName: raise RuntimeError("szOptionName is not set") if not self.bRemovable: raise RuntimeError("this options is not removable") - self.inifile.delKeyEx(self.szSectionName, self.szKeyName, None, value) - self.inifile.writeFile() + self.inifile.delkey_ex(self.szSectionName, self.szKeyName, None, value) + self.inifile.writefile() def get(self) -> Union[None, str]: if not self.szOptionName: raise RuntimeError("szOptionName not set") - res = self.inifile.getKeyValue(self.szSectionName, self.szKeyName) + res = self.inifile.getkeyvalue(self.szSectionName, self.szKeyName) return res @@ -157,8 +155,8 @@ class GameOption_UT2k4_HostName(GameOption_UT2k4): def set(self, value: str): super().set(value) - self.inifile.setAddKeyValue("Engine.GameReplicationInfo", "ShortName", value) - self.inifile.writeFile() + self.inifile.setaddkeyvalue("Engine.GameReplicationInfo", "ShortName", value) + self.inifile.writefile() @GameOptions_Factory_Register @@ -202,10 +200,10 @@ class GameOption_UT2k4_HTTPDownloadServer(GameOption_UT2k4): def set(self, value: str): super().set(value) - self.inifile.setAddKeyValue("IpDrv.HTTPDownload", "UseCompression", "True") - self.inifile.delKey("IpDrv.HTTPDownload", "ProxyServerHost") - self.inifile.delKey("IpDrv.HTTPDownload", "ProxyServerPort") - self.inifile.writeFile() + self.inifile.setaddkeyvalue("IpDrv.HTTPDownload", "UseCompression", "True") + self.inifile.delkey("IpDrv.HTTPDownload", "ProxyServerHost") + self.inifile.delkey("IpDrv.HTTPDownload", "ProxyServerPort") + self.inifile.writefile() @GameOptions_Factory_Register @@ -239,8 +237,8 @@ class GameOption_UT2k4_NetServerMaxTickRate(GameOption_UT2k4): def set(self, value: str): super().set(value) - self.inifile.setAddKeyValue("Engine.DemoRecDrive", "NetServerMaxTickRate", value) - self.inifile.writeFile() + self.inifile.setaddkeyvalue("Engine.DemoRecDrive", "NetServerMaxTickRate", value) + self.inifile.writefile() @GameOptions_Factory_Register @@ -254,8 +252,8 @@ class GameOption_UT2k4_LanServerMaxTickRate(GameOption_UT2k4): def set(self, value: str): super().set(value) - self.inifile.setAddKeyValue("Engine.DemoRecDrive", "LanServerMaxTickRate", value) - self.inifile.writeFile() + self.inifile.setaddkeyvalue("Engine.DemoRecDrive", "LanServerMaxTickRate", value) + self.inifile.writefile() @GameOptions_Factory_Register @@ -730,7 +728,7 @@ class GameOption_UT2k4_WebServer(GameOption_UT2k4): def set(self, value: str) -> None: super().set(value) if int(value) > 0: - self.inifile.setAddKeyValue("UWeb.WebServer", "bEnabled", "True") + self.inifile.setaddkeyvalue("UWeb.WebServer", "bEnabled", "True") else: - self.inifile.setAddKeyValue("UWeb.WebServer", "bEnabled", "False") - self.inifile.writeFile() + self.inifile.setaddkeyvalue("UWeb.WebServer", "bEnabled", "False") + self.inifile.writefile() diff --git a/src/pygamecfg/ut99.py b/src/pygamecfg/game_ut99.py similarity index 85% rename from src/pygamecfg/ut99.py rename to src/pygamecfg/game_ut99.py index 9f96423..5276e68 100644 --- a/src/pygamecfg/ut99.py +++ b/src/pygamecfg/game_ut99.py @@ -1,37 +1,25 @@ +# pylint: disable=duplicate-code,missing-class-docstring,missing-module-docstring,missing-function-docstring from __future__ import annotations from typing import Union -from pysimpleini import PySimpleINI, KeyNotFoundError, SectionNotFoundError, Section, Key from os.path import join +from pathlib import Path + +from pysimpleini import KeyNotFoundError, SectionNotFoundError from .core import GameOptions_Factory_Register, GameOption, OptionType +from .tool_ini import PySimpleINI_GroupKeysInSection -class PySimpleINI_UT99(PySimpleINI): - def GroupKeysInSection(self, szSectionName: str, szKeyName: str) -> None: - try: - _section = self.getSection(szSectionName) - if type(_section) is Section: - ar_ServerPackages = _section.getKey(szKeyName) - if isinstance(ar_ServerPackages, Key): - ar_ServerPackages = [ar_ServerPackages] - else: # array - pass - for ServerPackages in ar_ServerPackages: - _section.delKey(ServerPackages.name, None, ServerPackages.value) - for ServerPackages in ar_ServerPackages: - _section.setAddKeyValue(ServerPackages.name, ServerPackages.value, True) - except SectionNotFoundError: - pass - - def writeFile(self, bBeautify: bool = False) -> None: - self.GroupKeysInSection("XC_Engine.XC_GameEngine", "ServerPackages") - self.GroupKeysInSection("XC_Engine.XC_GameEngine", "ServerActors") - self.GroupKeysInSection("Engine.GameEngine", "ServerPackages") - self.GroupKeysInSection("Engine.GameEngine", "ServerActors") - self.GroupKeysInSection("Core.System", "Suppress") - self.GroupKeysInSection("Editor.EditorEngine", "EditPackages") - super().writeFile(bBeautify) +class PySimpleINI_UT99(PySimpleINI_GroupKeysInSection): + GroupRules = [ + ("XC_Engine.XC_GameEngine", "ServerPackages"), + ("XC_Engine.XC_GameEngine", "ServerActors"), + ("Engine.GameEngine", "ServerPackages"), + ("Engine.GameEngine", "ServerActors"), + ("Core.System", "Suppress"), + ("Editor.EditorEngine", "EditPackages"), + ] class GameOption_UT99(GameOption): @@ -44,17 +32,28 @@ class GameOption_UT99(GameOption): bForceAdd: bool = False bRemovable: bool = False + cachedFile: Union[None, PySimpleINI_UT99] = None + cachedFilePath: Union[None, Path] = None + + @classmethod + def openFile(cls, filepath: Path) -> PySimpleINI_UT99: + if (not GameOption_UT99.cachedFile) or (filepath != GameOption_UT99.cachedFilePath): + GameOption_UT99.cachedFilePath = filepath + GameOption_UT99.cachedFile = PySimpleINI_UT99(filepath) + return GameOption_UT99.cachedFile + def __init__(self, GameRootDir: str, ConfigFileRelPath: str) -> None: super().__init__(GameRootDir, ConfigFileRelPath) - self.mainConfigFilePath = join(GameRootDir, ConfigFileRelPath) - self.inifile = PySimpleINI_UT99(self.mainConfigFilePath) + self.mainConfigFilePath: Path = Path(join(GameRootDir, ConfigFileRelPath)) + # self.inifile = PySimpleINI_UT99(self.mainConfigFilePath) + self.inifile = GameOption_UT99.openFile(self.mainConfigFilePath) def set(self, value: str) -> None: if not self.szOptionName: raise RuntimeError("szOptionName is not set") self.format(value) - self.inifile.setAddKeyValue(self.szSectionName, self.szKeyName, self.szFormatedValue, self.bForceAdd) - self.inifile.writeFile() + self.inifile.setaddkeyvalue(self.szSectionName, self.szKeyName, self.szFormatedValue, self.bForceAdd) + self.inifile.writefile() def rem(self, value: Union[None, str]) -> None: if not self.szOptionName: @@ -62,7 +61,7 @@ class GameOption_UT99(GameOption): if not self.bRemovable: raise RuntimeError("this options is not removable") self.inifile.delkey_ex(self.szSectionName, self.szKeyName, None, value) - self.inifile.writeFile() + self.inifile.writefile() def get(self) -> Union[None, str]: if not self.szOptionName: @@ -174,8 +173,8 @@ class GameOption_UT99_HostName(GameOption_UT99): def set(self, value: str) -> None: super().set(value) - self.inifile.setAddKeyValue("Engine.GameReplicationInfo", "ShortName", value) - self.inifile.writeFile() + self.inifile.setaddkeyvalue("Engine.GameReplicationInfo", "ShortName", value) + self.inifile.writefile() @GameOptions_Factory_Register @@ -239,8 +238,8 @@ class GameOption_UT99_AdminName(GameOption_UT99): def set(self, value: str) -> None: super().set(value) - self.inifile.setAddKeyValue("UTServerAdmin.UTServerAdmin", "AdminUsername", value) - self.inifile.writeFile() + self.inifile.setaddkeyvalue("UTServerAdmin.UTServerAdmin", "AdminUsername", value) + self.inifile.writefile() @GameOptions_Factory_Register @@ -254,10 +253,10 @@ class GameOption_UT99_HTTPDownloadServer(GameOption_UT99): def set(self, value: str) -> None: super().set(value) - self.inifile.setAddKeyValue("IpDrv.HTTPDownload", "UseCompression", "True") - self.inifile.delKey("IpDrv.HTTPDownload", "ProxyServerHost") - self.inifile.delKey("IpDrv.HTTPDownload", "ProxyServerPort") - self.inifile.writeFile() + self.inifile.setaddkeyvalue("IpDrv.HTTPDownload", "UseCompression", "True") + self.inifile.delkey("IpDrv.HTTPDownload", "ProxyServerHost") + self.inifile.delkey("IpDrv.HTTPDownload", "ProxyServerPort") + self.inifile.writefile() @GameOptions_Factory_Register @@ -291,8 +290,8 @@ class GameOption_UT99_NetServerMaxTickRate(GameOption_UT99): def set(self, value: str) -> None: super().set(value) - self.inifile.setAddKeyValue("Engine.DemoRecDriver", "NetServerMaxTickRate", value) - self.inifile.writeFile() + self.inifile.setaddkeyvalue("Engine.DemoRecDriver", "NetServerMaxTickRate", value) + self.inifile.writefile() @GameOptions_Factory_Register @@ -306,8 +305,8 @@ class GameOption_UT99_LanServerMaxTickRate(GameOption_UT99): def set(self, value: str) -> None: super().set(value) - self.inifile.setAddKeyValue("Engine.DemoRecDriver", "LanServerMaxTickRate", value) - self.inifile.writeFile() + self.inifile.setaddkeyvalue("Engine.DemoRecDriver", "LanServerMaxTickRate", value) + self.inifile.writefile() @GameOptions_Factory_Register @@ -321,8 +320,8 @@ class GameOption_UT99_AdminPassword(GameOption_UT99): def set(self, value: str) -> None: super().set(value) - self.inifile.setAddKeyValue("UTServerAdmin.UTServerAdmin", "AdminPassword", value) - self.inifile.writeFile() + self.inifile.setaddkeyvalue("UTServerAdmin.UTServerAdmin", "AdminPassword", value) + self.inifile.writefile() def get(self) -> Union[None, str]: try: @@ -458,15 +457,15 @@ class GameOption_UT99_WebServer(GameOption_UT99): def set(self, value: str) -> None: # fix ut99 v469c try: - self.inifile.delKey("UWeb.WebServer", "Listenport") - except: + self.inifile.delkey("UWeb.WebServer", "Listenport") + except KeyNotFoundError: pass super().set(value) if int(value) > 0: - self.inifile.setAddKeyValue("UWeb.WebServer", "bEnabled", "True") + self.inifile.setaddkeyvalue("UWeb.WebServer", "bEnabled", "True") else: - self.inifile.setAddKeyValue("UWeb.WebServer", "bEnabled", "False") - self.inifile.writeFile() + self.inifile.setaddkeyvalue("UWeb.WebServer", "bEnabled", "False") + self.inifile.writefile() @GameOptions_Factory_Register diff --git a/src/pygamecfg/tool_ini.py b/src/pygamecfg/tool_ini.py new file mode 100644 index 0000000..b92bd00 --- /dev/null +++ b/src/pygamecfg/tool_ini.py @@ -0,0 +1,33 @@ +"""utility module that contain PySimpleINI based helpers""" +from __future__ import annotations + +from pysimpleini import PySimpleINI, SectionNotFoundError, Key + + +class PySimpleINI_GroupKeysInSection(PySimpleINI): + """a class base on PySimpleINI that allow user to force some key to be group together in a section""" + + GroupRules: list[tuple[str, str]] = [] + + def groupkeysinsection(self, szSectionName: str, szKeyName: str) -> None: + """internal function that actually group keys""" + try: + _section = self.getsection(szSectionName) + if len(_section) == 1: + ar_ServerPackages = _section[0].getkey(szKeyName) + if isinstance(ar_ServerPackages, Key): + ar_ServerPackages = [ar_ServerPackages] + else: # array + pass + for ServerPackages in ar_ServerPackages: + _section[0].delkey(ServerPackages.name, None, ServerPackages.value) + for ServerPackages in ar_ServerPackages: + _section[0].setaddkeyvalue(ServerPackages.name, ServerPackages.value, True) + except SectionNotFoundError: + pass + + def writefile(self, bBeautify: bool = False, bWipeComments: bool = False) -> None: + """overload of the write function to call the group function before""" + for GroupRule in self.GroupRules: + self.groupkeysinsection(GroupRule[0], GroupRule[1]) + super().writefile(bBeautify, bWipeComments) diff --git a/test/test_ut99.py b/test/test_ut99.py index b461be9..ee29412 100644 --- a/test/test_ut99.py +++ b/test/test_ut99.py @@ -19,6 +19,9 @@ from src.pygamecfg.__main__ import fct_main class Testtest_ut99(unittest.TestCase): + def test_normal_TESSTTT(self): + fct_main(["-v", "-g", "ut99", "-b", "test/data/UT99", "GetOption", "ServerPackages"]) + def test_normal_ServerPackages(self): with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "ServerPackages"]) @@ -95,6 +98,8 @@ class Testtest_ut99(unittest.TestCase): self.assertEqual("", capted_stderr.getvalue()) def test_normal_AdminEmail(self): + # fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "AdminEmail"]) + # return with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "AdminEmail"]) # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep -- 2.47.3 From d8c78e7fc1a332831bea8b20be12aa10af681608 Mon Sep 17 00:00:00 2001 From: cclecle Date: Sat, 23 Sep 2023 20:06:57 +0100 Subject: [PATCH 05/13] fix: dependency format --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1c862ff..fbd8f40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ 'importlib-metadata; python_version<"3.9"', 'packaging', 'pysimpleini>=0.3.1', - 'typed-argument-parser==1.x' + 'typed-argument-parser==1.*' ] dynamic = ["version"] -- 2.47.3 From 31b9f97d35831240b6b3006ef5765651fecddcec Mon Sep 17 00:00:00 2001 From: cclecle Date: Sat, 23 Sep 2023 20:19:31 +0100 Subject: [PATCH 06/13] fix: add missing type hint chore: remove unused directory --- src/pygamecfg/core.py | 6 +++--- src/pygamecfg/data/.keep | 0 src/pygamecfg/data/__init__.py | 7 ------- 3 files changed, 3 insertions(+), 10 deletions(-) delete mode 100644 src/pygamecfg/data/.keep delete mode 100644 src/pygamecfg/data/__init__.py diff --git a/src/pygamecfg/core.py b/src/pygamecfg/core.py index f0ae580..0f26134 100644 --- a/src/pygamecfg/core.py +++ b/src/pygamecfg/core.py @@ -32,15 +32,15 @@ class GameOption(metaclass=ABCMeta): self.GameRootDir = GameRootDir self.ConfigFileRelPath = ConfigFileRelPath - def __enter__(self): + def __enter__(self) -> GameOption: """contextlib enter hook""" return self - def __exit__(self, exception_type, exception_value, exception_traceback): + def __exit__(self, exception_type, exception_value, exception_traceback) -> None: """contextlib exit hook""" self.close() - def close(self): + def close(self) -> None: """user-define close() function (for subclassing)""" def format(self, value: Union[int, str, float]) -> None: diff --git a/src/pygamecfg/data/.keep b/src/pygamecfg/data/.keep deleted file mode 100644 index e69de29..0000000 diff --git a/src/pygamecfg/data/__init__.py b/src/pygamecfg/data/__init__.py deleted file mode 100644 index dec0d77..0000000 --- a/src/pygamecfg/data/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# pygamecfg (c) by chacha -# -# pygamecfg 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 . -- 2.47.3 From 99beee0937d09de2f8f97a736e4717c1dc57c954 Mon Sep 17 00:00:00 2001 From: cclecle Date: Sat, 23 Sep 2023 20:31:15 +0100 Subject: [PATCH 07/13] chore: improve type hint --- src/pygamecfg/__main__.py | 6 +++--- src/pygamecfg/core.py | 2 +- src/pygamecfg/tool_ini.py | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/pygamecfg/__main__.py b/src/pygamecfg/__main__.py index 5678e48..d531228 100644 --- a/src/pygamecfg/__main__.py +++ b/src/pygamecfg/__main__.py @@ -24,7 +24,7 @@ class pygamecfg_args_SetOption(Tap): option: str value: str = "" - def configure(self): + def configure(self) -> None: self.add_argument("option") self.add_argument("value") @@ -72,9 +72,9 @@ class pygamecfg_args(Tap): self.command: Union[str, None] = cast(Union[str, None], self.command) # pylint: disable=attribute-defined-outside-init -def fct_main(i_args: list[str]): +def fct_main(i_args: list[str]) -> None: """CLI main function""" - parser = pygamecfg_args(prog=__Name__, description=__Summuary__) + parser: pygamecfg_args = pygamecfg_args(prog=__Name__, description=__Summuary__) args: pygamecfg_args = parser.parse_args(i_args) diff --git a/src/pygamecfg/core.py b/src/pygamecfg/core.py index 0f26134..ea108a6 100644 --- a/src/pygamecfg/core.py +++ b/src/pygamecfg/core.py @@ -127,7 +127,7 @@ class GameOptions_Factory: raise RuntimeError("Option not found") -def GameOptions_Factory_Register(cls: type[GameOption]): +def GameOptions_Factory_Register(cls: type[GameOption]) -> type[GameOption]: """decorator to register game option concrete implementation""" GameOptions_Factory.GameOptionRegister(cls) return cls diff --git a/src/pygamecfg/tool_ini.py b/src/pygamecfg/tool_ini.py index b92bd00..526c3f6 100644 --- a/src/pygamecfg/tool_ini.py +++ b/src/pygamecfg/tool_ini.py @@ -1,7 +1,7 @@ """utility module that contain PySimpleINI based helpers""" from __future__ import annotations - -from pysimpleini import PySimpleINI, SectionNotFoundError, Key +from typing import Union +from pysimpleini import PySimpleINI, SectionNotFoundError, Key, Section class PySimpleINI_GroupKeysInSection(PySimpleINI): @@ -12,9 +12,9 @@ class PySimpleINI_GroupKeysInSection(PySimpleINI): def groupkeysinsection(self, szSectionName: str, szKeyName: str) -> None: """internal function that actually group keys""" try: - _section = self.getsection(szSectionName) + _section: list[Section] = self.getsection(szSectionName) if len(_section) == 1: - ar_ServerPackages = _section[0].getkey(szKeyName) + ar_ServerPackages: Union[list[Key], Key] = _section[0].getkey(szKeyName) if isinstance(ar_ServerPackages, Key): ar_ServerPackages = [ar_ServerPackages] else: # array -- 2.47.3 From 00919c081fd077fadd380882b1ac36acfe2dd782 Mon Sep 17 00:00:00 2001 From: cclecle Date: Sat, 23 Sep 2023 21:49:00 +0100 Subject: [PATCH 08/13] chore: complete / fix typing --- src/pygamecfg/core.py | 4 ++-- src/pygamecfg/game_cod4.py | 6 +++--- src/pygamecfg/game_ut2k4.py | 13 ++++++------- src/pygamecfg/game_ut99.py | 11 +++++------ src/pygamecfg/tool_ini.py | 4 ++-- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/pygamecfg/core.py b/src/pygamecfg/core.py index ea108a6..6846b70 100644 --- a/src/pygamecfg/core.py +++ b/src/pygamecfg/core.py @@ -74,7 +74,7 @@ class GameOption(metaclass=ABCMeta): raise NotImplementedError("method not implemented") @abstractmethod - def get(self) -> Union[None, str]: + def get(self) -> Union[str, list[str]]: """generic get function""" raise NotImplementedError("method not implemented") @@ -118,7 +118,7 @@ class GameOptions_Factory: return raise RuntimeError("Option not found") - def get(self, OptionName: str) -> Union[None, str]: + def get(self, OptionName: str) -> Union[str, list[str]]: """generic get function (API call)""" for _option in self.ar_Options_cls_filtered: if _option.szOptionName == OptionName: diff --git a/src/pygamecfg/game_cod4.py b/src/pygamecfg/game_cod4.py index e267071..278f997 100644 --- a/src/pygamecfg/game_cod4.py +++ b/src/pygamecfg/game_cod4.py @@ -22,7 +22,7 @@ class GameOption_COD4(GameOption): self.mainConfigFilePath = join(GameRootDir, ConfigFileRelPath) self.cfgfile = open(self.mainConfigFilePath, "r", encoding="utf8") # pylint: disable=consider-using-with - def close(self): + def close(self) -> None: self.cfgfile.close() def set(self, value: str) -> None: @@ -62,7 +62,7 @@ class GameOption_COD4(GameOption): ofile.write(newFile) self.cfgfile = open(self.mainConfigFilePath, "r", encoding="utf8") # pylint: disable=consider-using-with - def get(self) -> Union[None, str]: + def get(self) -> str: if not self.szOptionName: raise RuntimeError("szOptionName not set") @@ -71,7 +71,7 @@ class GameOption_COD4(GameOption): else: regex = r"^\s*" + self.szPrefix + r"\s+" + self.szOptionName + r"\s*(?P.*)" bfound = False - res = None + for line in self.cfgfile.readlines(): if result := re.search(regex, line): if bfound: diff --git a/src/pygamecfg/game_ut2k4.py b/src/pygamecfg/game_ut2k4.py index f041511..2c2b7bb 100644 --- a/src/pygamecfg/game_ut2k4.py +++ b/src/pygamecfg/game_ut2k4.py @@ -62,11 +62,10 @@ class GameOption_UT2k4(GameOption): self.inifile.delkey_ex(self.szSectionName, self.szKeyName, None, value) self.inifile.writefile() - def get(self) -> Union[None, str]: + def get(self) -> Union[str, list[str]]: if not self.szOptionName: raise RuntimeError("szOptionName not set") - res = self.inifile.getkeyvalue(self.szSectionName, self.szKeyName) - return res + return self.inifile.getkeyvalue(self.szSectionName, self.szKeyName) class GameOption_UT2k4_GenAdd(GameOption_UT2k4): @@ -153,7 +152,7 @@ class GameOption_UT2k4_HostName(GameOption_UT2k4): szDefaultValue = "ChaCha Test Server" szHelp = "Server's HostName" - def set(self, value: str): + def set(self, value: str) -> None: super().set(value) self.inifile.setaddkeyvalue("Engine.GameReplicationInfo", "ShortName", value) self.inifile.writefile() @@ -198,7 +197,7 @@ class GameOption_UT2k4_HTTPDownloadServer(GameOption_UT2k4): szDefaultValue = "http://chacha.ddns.net/games/ut2k4" szHelp = "FastDL url" - def set(self, value: str): + def set(self, value: str) -> None: super().set(value) self.inifile.setaddkeyvalue("IpDrv.HTTPDownload", "UseCompression", "True") self.inifile.delkey("IpDrv.HTTPDownload", "ProxyServerHost") @@ -235,7 +234,7 @@ class GameOption_UT2k4_NetServerMaxTickRate(GameOption_UT2k4): szDefaultValue = "60" szHelp = "Server Max TickRate" - def set(self, value: str): + def set(self, value: str) -> None: super().set(value) self.inifile.setaddkeyvalue("Engine.DemoRecDrive", "NetServerMaxTickRate", value) self.inifile.writefile() @@ -250,7 +249,7 @@ class GameOption_UT2k4_LanServerMaxTickRate(GameOption_UT2k4): szDefaultValue = "60" szHelp = "Lan Server Max TickRate" - def set(self, value: str): + def set(self, value: str) -> None: super().set(value) self.inifile.setaddkeyvalue("Engine.DemoRecDrive", "LanServerMaxTickRate", value) self.inifile.writefile() diff --git a/src/pygamecfg/game_ut99.py b/src/pygamecfg/game_ut99.py index 5276e68..21b5b11 100644 --- a/src/pygamecfg/game_ut99.py +++ b/src/pygamecfg/game_ut99.py @@ -63,11 +63,10 @@ class GameOption_UT99(GameOption): self.inifile.delkey_ex(self.szSectionName, self.szKeyName, None, value) self.inifile.writefile() - def get(self) -> Union[None, str]: + def get(self) -> Union[str, list[str]]: if not self.szOptionName: raise RuntimeError("szOptionName not set") - res = self.inifile.getkeyvalue(self.szSectionName, self.szKeyName) - return res + return self.inifile.getkeyvalue(self.szSectionName, self.szKeyName) class GameOption_UT99_GenAdd(GameOption_UT99): @@ -94,7 +93,7 @@ class GameOption_UT99_GenAdd__Engine(GameOption_UT99_GenAdd): prev = self.szSectionName try: - self.inifile.getSection("XC_Engine.XC_GameEngine") + self.inifile.getsection("XC_Engine.XC_GameEngine") self.szSectionName = "XC_Engine.XC_GameEngine" super().set(value) except SectionNotFoundError: @@ -108,7 +107,7 @@ class GameOption_UT99_GenAdd__Engine(GameOption_UT99_GenAdd): prev = self.szSectionName try: - self.inifile.getSection("XC_Engine.XC_GameEngine") + self.inifile.getsection("XC_Engine.XC_GameEngine") self.szSectionName = "XC_Engine.XC_GameEngine" super().rem(value) except SectionNotFoundError: @@ -323,7 +322,7 @@ class GameOption_UT99_AdminPassword(GameOption_UT99): self.inifile.setaddkeyvalue("UTServerAdmin.UTServerAdmin", "AdminPassword", value) self.inifile.writefile() - def get(self) -> Union[None, str]: + def get(self) -> Union[str, list[str]]: try: return super().get() except KeyNotFoundError: diff --git a/src/pygamecfg/tool_ini.py b/src/pygamecfg/tool_ini.py index 526c3f6..d49b2bf 100644 --- a/src/pygamecfg/tool_ini.py +++ b/src/pygamecfg/tool_ini.py @@ -20,9 +20,9 @@ class PySimpleINI_GroupKeysInSection(PySimpleINI): else: # array pass for ServerPackages in ar_ServerPackages: - _section[0].delkey(ServerPackages.name, None, ServerPackages.value) + _section[0].delkey(ServerPackages.getname(), None, ServerPackages.getvalue()) for ServerPackages in ar_ServerPackages: - _section[0].setaddkeyvalue(ServerPackages.name, ServerPackages.value, True) + _section[0].setaddkeyvalue(ServerPackages.getname(), ServerPackages.getvalue(), True) except SectionNotFoundError: pass -- 2.47.3 From 5d0ff79d05d83d3d1915e2406d6a3e9bfac930ed Mon Sep 17 00:00:00 2001 From: cclecle Date: Sat, 23 Sep 2023 22:06:52 +0100 Subject: [PATCH 09/13] fix: type hint chore: extract common UT GameOption(s) --- src/pygamecfg/common_ut.py | 61 +++++++++++++++++++++++++++++++++++++ src/pygamecfg/game_cod4.py | 2 +- src/pygamecfg/game_ut2k4.py | 54 ++++---------------------------- src/pygamecfg/game_ut99.py | 54 +++----------------------------- test/test_gen.py | 3 -- test/test_ut99.py | 8 ----- 6 files changed, 73 insertions(+), 109 deletions(-) create mode 100644 src/pygamecfg/common_ut.py diff --git a/src/pygamecfg/common_ut.py b/src/pygamecfg/common_ut.py new file mode 100644 index 0000000..874e268 --- /dev/null +++ b/src/pygamecfg/common_ut.py @@ -0,0 +1,61 @@ +"""common UT functions""" +from __future__ import annotations +from typing import Union + +from os.path import join +from pathlib import Path + +from pysimpleini import PySimpleINI + +from .core import GameOption, OptionType + + +class GameOption_UT(GameOption): + """generic UT Option class""" + + szGameType = "" + TValueType = OptionType.OT_INVALID + + szOptionName = "" + szSectionName = "" + szKeyName = "" + bForceAdd: bool = False + bRemovable: bool = False + + cachedFile: Union[None, PySimpleINI] = None + cachedFilePath: Union[None, Path] = None + + Cls_PySimpleINI: type[PySimpleINI] = PySimpleINI + + @classmethod + def openFile(cls, filepath: Path) -> PySimpleINI: + """Open the file""" + if (not cls.cachedFile) or (filepath != cls.cachedFilePath): + cls.cachedFilePath = filepath + cls.cachedFile = cls.Cls_PySimpleINI(filepath) + return cls.cachedFile + + def __init__(self, GameRootDir: str, ConfigFileRelPath: str) -> None: + super().__init__(GameRootDir, ConfigFileRelPath) + self.mainConfigFilePath: Path = Path(join(GameRootDir, ConfigFileRelPath)) + self.inifile = self.openFile(self.mainConfigFilePath) + + def set(self, value: str) -> None: + if not self.szOptionName: + raise RuntimeError("szOptionName is not set") + self.format(value) + self.inifile.setaddkeyvalue(self.szSectionName, self.szKeyName, self.szFormatedValue, self.bForceAdd) + self.inifile.writefile() + + def rem(self, value: Union[None, str]) -> None: + if not self.szOptionName: + raise RuntimeError("szOptionName is not set") + if not self.bRemovable: + raise RuntimeError("this options is not removable") + self.inifile.delkey_ex(self.szSectionName, self.szKeyName, None, value) + self.inifile.writefile() + + def get(self) -> Union[str, list[str]]: + if not self.szOptionName: + raise RuntimeError("szOptionName not set") + return self.inifile.getkeyvalue(self.szSectionName, self.szKeyName) diff --git a/src/pygamecfg/game_cod4.py b/src/pygamecfg/game_cod4.py index 278f997..288e654 100644 --- a/src/pygamecfg/game_cod4.py +++ b/src/pygamecfg/game_cod4.py @@ -1,4 +1,4 @@ -# pylint: disable=duplicate-code,missing-class-docstring,missing-module-docstring,missing-function-docstring +# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,duplicate-code from __future__ import annotations from typing import Union diff --git a/src/pygamecfg/game_ut2k4.py b/src/pygamecfg/game_ut2k4.py index 2c2b7bb..796a91e 100644 --- a/src/pygamecfg/game_ut2k4.py +++ b/src/pygamecfg/game_ut2k4.py @@ -1,16 +1,15 @@ -# pylint: disable=duplicate-code,missing-class-docstring,missing-module-docstring,missing-function-docstring +# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,duplicate-code from __future__ import annotations from typing import Union -from os.path import join -from pathlib import Path from pysimpleini import KeyNotFoundError -from .core import GameOptions_Factory_Register, GameOption, OptionType +from .core import GameOptions_Factory_Register, OptionType from .tool_ini import PySimpleINI_GroupKeysInSection +from .common_ut import GameOption_UT -class ChaChaSimpleINI_UT2k4(PySimpleINI_GroupKeysInSection): +class PySimpleINI_UT2k4(PySimpleINI_GroupKeysInSection): GroupRules = [ ("Engine.GameEngine", "ServerPackages"), ("Engine.GameEngine", "ServerActors"), @@ -22,50 +21,9 @@ class ChaChaSimpleINI_UT2k4(PySimpleINI_GroupKeysInSection): ] -class GameOption_UT2k4(GameOption): +class GameOption_UT2k4(GameOption_UT): szGameType = "ut2k4" - TValueType = OptionType.OT_INVALID - - szOptionName = "" - szSectionName = "" - szKeyName = "" - bForceAdd: bool = False - bRemovable: bool = False - - cachedFile: Union[None, ChaChaSimpleINI_UT2k4] = None - cachedFilePath: Union[None, Path] = None - - @classmethod - def openFile(cls, filepath: Path) -> ChaChaSimpleINI_UT2k4: - if (not GameOption_UT2k4.cachedFile) or (filepath != GameOption_UT2k4.cachedFilePath): - GameOption_UT2k4.cachedFilePath = filepath - GameOption_UT2k4.cachedFile = ChaChaSimpleINI_UT2k4(filepath) - return GameOption_UT2k4.cachedFile - - def __init__(self, GameRootDir: str, ConfigFileRelPath: str) -> None: - super().__init__(GameRootDir, ConfigFileRelPath) - self.mainConfigFilePath: Path = Path(join(GameRootDir, ConfigFileRelPath)) - self.inifile = ChaChaSimpleINI_UT2k4(self.mainConfigFilePath) - - def set(self, value: str) -> None: - if not self.szOptionName: - raise RuntimeError("szOptionName is not set") - self.format(value) - self.inifile.setaddkeyvalue(self.szSectionName, self.szKeyName, self.szFormatedValue, self.bForceAdd) - self.inifile.writefile() - - def rem(self, value: Union[None, str]) -> None: - if not self.szOptionName: - raise RuntimeError("szOptionName is not set") - if not self.bRemovable: - raise RuntimeError("this options is not removable") - self.inifile.delkey_ex(self.szSectionName, self.szKeyName, None, value) - self.inifile.writefile() - - def get(self) -> Union[str, list[str]]: - if not self.szOptionName: - raise RuntimeError("szOptionName not set") - return self.inifile.getkeyvalue(self.szSectionName, self.szKeyName) + Cls_PySimpleINI = PySimpleINI_UT2k4 class GameOption_UT2k4_GenAdd(GameOption_UT2k4): diff --git a/src/pygamecfg/game_ut99.py b/src/pygamecfg/game_ut99.py index 21b5b11..0e7bec9 100644 --- a/src/pygamecfg/game_ut99.py +++ b/src/pygamecfg/game_ut99.py @@ -1,14 +1,12 @@ -# pylint: disable=duplicate-code,missing-class-docstring,missing-module-docstring,missing-function-docstring +# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,duplicate-code from __future__ import annotations from typing import Union -from os.path import join -from pathlib import Path - from pysimpleini import KeyNotFoundError, SectionNotFoundError -from .core import GameOptions_Factory_Register, GameOption, OptionType +from .core import GameOptions_Factory_Register, OptionType from .tool_ini import PySimpleINI_GroupKeysInSection +from .common_ut import GameOption_UT class PySimpleINI_UT99(PySimpleINI_GroupKeysInSection): @@ -22,51 +20,9 @@ class PySimpleINI_UT99(PySimpleINI_GroupKeysInSection): ] -class GameOption_UT99(GameOption): +class GameOption_UT99(GameOption_UT): szGameType = "ut99" - TValueType = OptionType.OT_INVALID - - szOptionName = "" - szSectionName = "" - szKeyName = "" - bForceAdd: bool = False - bRemovable: bool = False - - cachedFile: Union[None, PySimpleINI_UT99] = None - cachedFilePath: Union[None, Path] = None - - @classmethod - def openFile(cls, filepath: Path) -> PySimpleINI_UT99: - if (not GameOption_UT99.cachedFile) or (filepath != GameOption_UT99.cachedFilePath): - GameOption_UT99.cachedFilePath = filepath - GameOption_UT99.cachedFile = PySimpleINI_UT99(filepath) - return GameOption_UT99.cachedFile - - def __init__(self, GameRootDir: str, ConfigFileRelPath: str) -> None: - super().__init__(GameRootDir, ConfigFileRelPath) - self.mainConfigFilePath: Path = Path(join(GameRootDir, ConfigFileRelPath)) - # self.inifile = PySimpleINI_UT99(self.mainConfigFilePath) - self.inifile = GameOption_UT99.openFile(self.mainConfigFilePath) - - def set(self, value: str) -> None: - if not self.szOptionName: - raise RuntimeError("szOptionName is not set") - self.format(value) - self.inifile.setaddkeyvalue(self.szSectionName, self.szKeyName, self.szFormatedValue, self.bForceAdd) - self.inifile.writefile() - - def rem(self, value: Union[None, str]) -> None: - if not self.szOptionName: - raise RuntimeError("szOptionName is not set") - if not self.bRemovable: - raise RuntimeError("this options is not removable") - self.inifile.delkey_ex(self.szSectionName, self.szKeyName, None, value) - self.inifile.writefile() - - def get(self) -> Union[str, list[str]]: - if not self.szOptionName: - raise RuntimeError("szOptionName not set") - return self.inifile.getkeyvalue(self.szSectionName, self.szKeyName) + Cls_PySimpleINI = PySimpleINI_UT99 class GameOption_UT99_GenAdd(GameOption_UT99): diff --git a/test/test_gen.py b/test/test_gen.py index c6e088c..faad99f 100644 --- a/test/test_gen.py +++ b/test/test_gen.py @@ -10,9 +10,6 @@ import unittest from io import StringIO from contextlib import redirect_stdout, redirect_stderr -print(__name__) -print(__package__) - from src import pygamecfg from src.pygamecfg.__main__ import fct_main diff --git a/test/test_ut99.py b/test/test_ut99.py index ee29412..b650efe 100644 --- a/test/test_ut99.py +++ b/test/test_ut99.py @@ -11,17 +11,11 @@ from os import linesep from io import StringIO from contextlib import redirect_stdout, redirect_stderr -print(__name__) -print(__package__) - from src import pygamecfg from src.pygamecfg.__main__ import fct_main class Testtest_ut99(unittest.TestCase): - def test_normal_TESSTTT(self): - fct_main(["-v", "-g", "ut99", "-b", "test/data/UT99", "GetOption", "ServerPackages"]) - def test_normal_ServerPackages(self): with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "ServerPackages"]) @@ -98,8 +92,6 @@ class Testtest_ut99(unittest.TestCase): self.assertEqual("", capted_stderr.getvalue()) def test_normal_AdminEmail(self): - # fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "AdminEmail"]) - # return with redirect_stdout(StringIO()) as capted_stdout, redirect_stderr(StringIO()) as capted_stderr: fct_main(["-g", "ut99", "-b", "test/data/UT99", "GetOption", "AdminEmail"]) # /!\ add '\n' at the end of the string cause Python terminal newline is always this, regardless Windows / Linux os.linesep -- 2.47.3 From af2aebd110c58baf6127ab7519be5cea29100f3d Mon Sep 17 00:00:00 2001 From: cclecle Date: Sat, 23 Sep 2023 23:37:11 +0100 Subject: [PATCH 10/13] fix: try to fix doc --- .settings/org.eclipse.core.resources.prefs | 7 ++++ README.md | 40 +--------------------- docs-static/usage.md | 17 +-------- mkdocs.yml | 36 ++++++++++--------- pyproject.toml | 6 ++-- src/pygamecfg/__init__.py | 7 ++-- src/pygamecfg/__main__.py | 6 ++-- src/pygamecfg/common_ut.py | 11 ++++++ src/pygamecfg/core.py | 21 ++++++++++++ src/pygamecfg/game_cod4.py | 11 ++++++ src/pygamecfg/game_ut2k4.py | 14 +++++++- src/pygamecfg/game_ut99.py | 14 +++++++- src/pygamecfg/tool_ini.py | 11 ++++++ 13 files changed, 121 insertions(+), 80 deletions(-) diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs index f4a6dfe..b041e9c 100644 --- a/.settings/org.eclipse.core.resources.prefs +++ b/.settings/org.eclipse.core.resources.prefs @@ -1,3 +1,10 @@ eclipse.preferences.version=1 +encoding//src/pygamecfg/__init__.py=utf-8 encoding//src/pygamecfg/__main__.py=utf-8 +encoding//src/pygamecfg/common_ut.py=utf-8 +encoding//src/pygamecfg/core.py=utf-8 +encoding//src/pygamecfg/game_cod4.py=utf-8 +encoding//src/pygamecfg/game_ut2k4.py=utf-8 +encoding//src/pygamecfg/game_ut99.py=utf-8 +encoding//src/pygamecfg/tool_ini.py=utf-8 encoding/=UTF-8 diff --git a/README.md b/README.md index 82c6bd2..efa5f28 100644 --- a/README.md +++ b/README.md @@ -8,46 +8,8 @@ ![](docs-static/Library.jpg) -# Python project template +# pyGameCFG -A nice template to start blank python projets. - -This template automate a lot of handy things and allow CI/CD automatic releases generation. - -It is also collectings data to feed Jenkins build. Checkout [Latest Documentation](https://chacha.ddns.net/mkdocs-web/chacha/{{repository}}/{{branch}}/latest/). -## Features - -### Generic pipeline skeleton: - - Prepare - - GetCode - - BuildPackage - - Install - - CheckCode - - PlotMetrics - - RunUnitTests - - GenDOC - - PostRelease - -### CI/CD Environment - - Jenkins - - Gitea (with patch for dynamic Readme variables: https://chacha.ddns.net/gitea/chacha/GiteaMarkupVariable) - - Docker - - MkDocsWeb - -### CI/CD Helper libs - - VirtualEnv - - Changelog generation based on commits - - copier - - pylint + pylint_json2html - - mypy - - unittest + xmlrunner + junitparser + junit2htmlreport - - mkdocs - -### Python project - - Full .toml implementation - - .whl automatic generation - - dynamic versionning using git repository - - embedded unit-test \ No newline at end of file diff --git a/docs-static/usage.md b/docs-static/usage.md index 7763bbd..fc56ab5 100644 --- a/docs-static/usage.md +++ b/docs-static/usage.md @@ -1,16 +1 @@ -# Usage - -## Pulvinar dolor -Donec dapibus est fermentum justo volutpat condimentum. Integer quis nunc neque. Donec dictum vehicula justo, in facilisis ex tincidunt in. -Vivamus sollicitudin sem dui, id mollis orci facilisis ut. Proin sed pulvinar dolor. Donec volutpat commodo urna imperdiet pulvinar. Fusce eget aliquam risus. -Vivamus viverra luctus ex, in finibus mi. Nullam elementum dapibus mollis. Ut suscipit volutpat ex, quis feugiat lacus consectetur eu. - -## Condimentum faucibus -Quisque auctor egestas sem, luctus suscipit ex maximus vitae. Duis facilisis augue et condimentum faucibus. -Donec cursus, enim a sagittis egestas, lectus lorem eleifend libero, at tincidunt leo magna at libero. -Nunc eros velit, suscipit luctus tempor vel, finibus et est. Curabitur efficitur pretium pulvinar. -Donec urna lectus, vulputate quis turpis sed, placerat congue urna. Phasellus aliquet fermentum quam, non auctor elit porta nec. Morbi eu ligula at nisl ultricies condimentum vitae id ante. - -## Aliquam lacinia -In volutpat lorem ex, et fringilla nibh faucibus quis. Mauris et arcu elementum, auctor dui vitae, egestas arcu. Duis sit amet aliquam quam. -Phasellus a odio turpis. Etiam tristique mi eu enim varius, eget facilisis est vestibulum. Aliquam lacinia nec purus sed luctus. Cras at laoreet erat. \ No newline at end of file +# Usage \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index a76672d..6c9c67e 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,19 +1,12 @@ -# pyChaChaDummyProject (c) by chacha -# -# pyChaChaDummyProject 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 . - docs_dir: docs -site_name: 'pygamecfg' -site_url: 'https://chacha.ddns.net/mkdocs-web/chacha/pygamecfg/latest/' -site_description: 'A simple game config tool that provide bash API to read / write game config files (cod, ut ...)' -site_author: 'chacha' -repo_url: 'https://chacha.ddns.net/gitea/chacha/pygamecfg' +site_name: pygamecfg +site_url: https://chacha.ddns.net/mkdocs-web/chacha/pygamecfg/latest/ +site_description: A simple game config tool that provide bash API to read / write + game config files (cod, ut ...) +site_author: chacha +repo_url: https://chacha.ddns.net/gitea/chacha/pygamecfg use_directory_urls: false -copyright: 'CC BY-NC-SA 4.0' +copyright: CC BY-NC-SA 4.0 theme: name: material features: @@ -44,17 +37,21 @@ theme: icon: material/brightness-7 name: Switch to dark mode plugins: +- localsearch - search +- autorefs - markdownextradata - mermaid2 -- localsearch - mkdocstrings: default_handler: python handlers: python: + paths: + - src options: filters: - '!^_[^_]' + docstring_style: google inherited_members: true show_if_no_docstring: true show_signature_annotations: true @@ -65,6 +62,13 @@ plugins: show_root_full_path: false merge_init_into_class: true separate_signature: true +- with-pdf: + cover_subtitle: User Manual + cover_logo: C:\Users\chacha\git\pygamecfg\docs-static\Library.jpg + verbose: false + exclude_pages: + - LICENSE + output_path: C:\Users\chacha\git\pygamecfg\helpers-results\doc_gen\site\pdf\manual.pdf markdown_extensions: - def_list - tables @@ -115,4 +119,4 @@ markdown_extensions: emoji_generator: !!python/name:materialx.emoji.to_svg extra: branch: master - repository: pygitversionhelper + repository: pygamecfg diff --git a/pyproject.toml b/pyproject.toml index fbd8f40..1fb8af4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,8 +61,8 @@ coverage-check = ["coverage>=7.0"] complexity-check = ["radon>=5.1"] quality-check = ["pylint>=2.15","pylint-json2html>=0.4","pandas>=1.5"] type-check = ["mypy[reports]>=0.99" ] -doc-gen = ["mkdocs>=1.4.0", "mkdocs-material>=8.5","mkdocs-pymdownx-material-extras", "mkdocs-localsearch>=0.9.0", "mkdocstrings[python]>=0.19", "mkdocs-with-pdf>=0.9.3","pyyaml>=6.0","pymdown-extensions>=9","mkdocs-markdownextradata-plugin","mkdocs-mermaid2-plugin"] +doc-gen = ["mkdocs>=1.4.0", "mkdocs-material>=8.5","mkdocs-material-extensions","mkdocs-pymdownx-material-extras", "mkdocs-localsearch>=0.9.0", "mkdocstrings[python]>=0.19", "mkdocs-with-pdf>=0.9.3","pyyaml>=6.0","pymdown-extensions>=9","mkdocs-markdownextradata-plugin","mkdocs-mermaid2-plugin","mkdocs-autorefs"] -#[project.scripts] -#my-script = "my_package.module:function" +# [project.scripts] +# my-script = "my_package.module:function" diff --git a/src/pygamecfg/__init__.py b/src/pygamecfg/__init__.py index c6fcc12..48ec280 100644 --- a/src/pygamecfg/__init__.py +++ b/src/pygamecfg/__init__.py @@ -1,6 +1,9 @@ -# pygamecfg (c) by chacha +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# pyGameCFG(c) by chacha # -# pygamecfg is licensed under a +# pyGameCFG 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 diff --git a/src/pygamecfg/__main__.py b/src/pygamecfg/__main__.py index d531228..ff3c678 100644 --- a/src/pygamecfg/__main__.py +++ b/src/pygamecfg/__main__.py @@ -1,12 +1,14 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# PySimpleINI (c) by chacha + +# pyGameCFG(c) by chacha # -# PySimpleINI is licensed under a +# pyGameCFG 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 . + """CLI interface module""" from __future__ import annotations from typing import Literal, cast, Union diff --git a/src/pygamecfg/common_ut.py b/src/pygamecfg/common_ut.py index 874e268..5fd67b0 100644 --- a/src/pygamecfg/common_ut.py +++ b/src/pygamecfg/common_ut.py @@ -1,3 +1,14 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# pyGameCFG(c) by chacha +# +# pyGameCFG 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 . + """common UT functions""" from __future__ import annotations from typing import Union diff --git a/src/pygamecfg/core.py b/src/pygamecfg/core.py index 6846b70..d5abe22 100644 --- a/src/pygamecfg/core.py +++ b/src/pygamecfg/core.py @@ -1,3 +1,14 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# pyGameCFG(c) by chacha +# +# pyGameCFG 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 . + """ Core file of pygamecfg contain generic management code for GameOption """ @@ -29,6 +40,16 @@ class GameOption(metaclass=ABCMeta): szFormatedValue: str = "" def __init__(self, GameRootDir: str, ConfigFileRelPath: Union[None, str] = None): + """GameOption constructor. + + ///warning + This object does not aim to be created + /// + + Args: + GameRootDir: root dir of the game + ConfigFileRelPath: path to the configfile (relative to rootdir) + """ self.GameRootDir = GameRootDir self.ConfigFileRelPath = ConfigFileRelPath diff --git a/src/pygamecfg/game_cod4.py b/src/pygamecfg/game_cod4.py index 288e654..063e4af 100644 --- a/src/pygamecfg/game_cod4.py +++ b/src/pygamecfg/game_cod4.py @@ -1,3 +1,14 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# pyGameCFG(c) by chacha +# +# pyGameCFG 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 . + # pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,duplicate-code from __future__ import annotations from typing import Union diff --git a/src/pygamecfg/game_ut2k4.py b/src/pygamecfg/game_ut2k4.py index 796a91e..ca97adb 100644 --- a/src/pygamecfg/game_ut2k4.py +++ b/src/pygamecfg/game_ut2k4.py @@ -1,4 +1,16 @@ -# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,duplicate-code +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# pyGameCFG(c) by chacha +# +# pyGameCFG 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 . + +# pylint: disable=missing-class-docstring,missing-function-docstring,duplicate-code +"""UT2k4 command set""" from __future__ import annotations from typing import Union diff --git a/src/pygamecfg/game_ut99.py b/src/pygamecfg/game_ut99.py index 0e7bec9..298999c 100644 --- a/src/pygamecfg/game_ut99.py +++ b/src/pygamecfg/game_ut99.py @@ -1,4 +1,16 @@ -# pylint: disable=missing-class-docstring,missing-module-docstring,missing-function-docstring,duplicate-code +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# pyGameCFG(c) by chacha +# +# pyGameCFG 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 . + +# pylint: disable=missing-class-docstring,missing-function-docstring,duplicate-code +"""UT99 command set""" from __future__ import annotations from typing import Union diff --git a/src/pygamecfg/tool_ini.py b/src/pygamecfg/tool_ini.py index d49b2bf..5526da5 100644 --- a/src/pygamecfg/tool_ini.py +++ b/src/pygamecfg/tool_ini.py @@ -1,3 +1,14 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# pyGameCFG(c) by chacha +# +# pyGameCFG 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 . + """utility module that contain PySimpleINI based helpers""" from __future__ import annotations from typing import Union -- 2.47.3 From d9f133bb146aca0b3ba784129a7c2a43c34510ff Mon Sep 17 00:00:00 2001 From: cclecle Date: Sun, 24 Sep 2023 11:00:42 +0100 Subject: [PATCH 11/13] test: try to fix doc gen --- .settings/org.eclipse.core.resources.prefs | 2 +- docs-static/usage.md | 2 +- mkdocs.yml | 9 ++------- src/pygamecfg/__init__.py | 11 ++++++----- src/pygamecfg/common_ut.py | 2 +- src/pygamecfg/{core.py => core_gamecfg.py} | 0 src/pygamecfg/game_cod4.py | 2 +- src/pygamecfg/game_ut2k4.py | 2 +- src/pygamecfg/game_ut99.py | 2 +- src/pygamecfg/py.typed | 1 + 10 files changed, 15 insertions(+), 18 deletions(-) rename src/pygamecfg/{core.py => core_gamecfg.py} (100%) create mode 100644 src/pygamecfg/py.typed diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs index b041e9c..bf70918 100644 --- a/.settings/org.eclipse.core.resources.prefs +++ b/.settings/org.eclipse.core.resources.prefs @@ -2,7 +2,7 @@ eclipse.preferences.version=1 encoding//src/pygamecfg/__init__.py=utf-8 encoding//src/pygamecfg/__main__.py=utf-8 encoding//src/pygamecfg/common_ut.py=utf-8 -encoding//src/pygamecfg/core.py=utf-8 +encoding//src/pygamecfg/core_gamecfg.py=utf-8 encoding//src/pygamecfg/game_cod4.py=utf-8 encoding//src/pygamecfg/game_ut2k4.py=utf-8 encoding//src/pygamecfg/game_ut99.py=utf-8 diff --git a/docs-static/usage.md b/docs-static/usage.md index fc56ab5..8f04b05 100644 --- a/docs-static/usage.md +++ b/docs-static/usage.md @@ -1 +1 @@ -# Usage \ No newline at end of file +# Usage diff --git a/mkdocs.yml b/mkdocs.yml index 6c9c67e..335b00f 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,8 +1,7 @@ docs_dir: docs site_name: pygamecfg site_url: https://chacha.ddns.net/mkdocs-web/chacha/pygamecfg/latest/ -site_description: A simple game config tool that provide bash API to read / write - game config files (cod, ut ...) +site_description: A simple ini parser / factory site_author: chacha repo_url: https://chacha.ddns.net/gitea/chacha/pygamecfg use_directory_urls: false @@ -37,21 +36,17 @@ theme: icon: material/brightness-7 name: Switch to dark mode plugins: -- localsearch - search -- autorefs - markdownextradata - mermaid2 +- localsearch - mkdocstrings: default_handler: python handlers: python: - paths: - - src options: filters: - '!^_[^_]' - docstring_style: google inherited_members: true show_if_no_docstring: true show_signature_annotations: true diff --git a/src/pygamecfg/__init__.py b/src/pygamecfg/__init__.py index 48ec280..2e38913 100644 --- a/src/pygamecfg/__init__.py +++ b/src/pygamecfg/__init__.py @@ -16,11 +16,6 @@ Main module __init__ file. from importlib.metadata import distribution, version, PackageNotFoundError import warnings -from .core import GameOptions_Factory - -from . import game_cod4 -from . import game_ut99 -from . import game_ut2k4 try: # pragma: no cover __version__ = version("pygamecfg") @@ -41,3 +36,9 @@ try: # pragma: no cover except PackageNotFoundError: # pragma: no cover warnings.warn('can not read dist.metadata["Name"], assuming local test context, setting it to ') __Name__ = "pygamecfg" + +from pygamecfg.core_gamecfg import GameOptions_Factory + +from . import game_cod4 +from . import game_ut99 +from . import game_ut2k4 diff --git a/src/pygamecfg/common_ut.py b/src/pygamecfg/common_ut.py index 5fd67b0..cf79dbb 100644 --- a/src/pygamecfg/common_ut.py +++ b/src/pygamecfg/common_ut.py @@ -18,7 +18,7 @@ from pathlib import Path from pysimpleini import PySimpleINI -from .core import GameOption, OptionType +from .core_gamecfg import GameOption, OptionType class GameOption_UT(GameOption): diff --git a/src/pygamecfg/core.py b/src/pygamecfg/core_gamecfg.py similarity index 100% rename from src/pygamecfg/core.py rename to src/pygamecfg/core_gamecfg.py diff --git a/src/pygamecfg/game_cod4.py b/src/pygamecfg/game_cod4.py index 063e4af..be19ac6 100644 --- a/src/pygamecfg/game_cod4.py +++ b/src/pygamecfg/game_cod4.py @@ -16,7 +16,7 @@ from typing import Union import re from os.path import join -from .core import GameOptions_Factory_Register, GameOption, OptionType +from .core_gamecfg import GameOptions_Factory_Register, GameOption, OptionType class GameOption_COD4(GameOption): diff --git a/src/pygamecfg/game_ut2k4.py b/src/pygamecfg/game_ut2k4.py index ca97adb..07c2d96 100644 --- a/src/pygamecfg/game_ut2k4.py +++ b/src/pygamecfg/game_ut2k4.py @@ -16,7 +16,7 @@ from typing import Union from pysimpleini import KeyNotFoundError -from .core import GameOptions_Factory_Register, OptionType +from .core_gamecfg import GameOptions_Factory_Register, OptionType from .tool_ini import PySimpleINI_GroupKeysInSection from .common_ut import GameOption_UT diff --git a/src/pygamecfg/game_ut99.py b/src/pygamecfg/game_ut99.py index 298999c..781ed72 100644 --- a/src/pygamecfg/game_ut99.py +++ b/src/pygamecfg/game_ut99.py @@ -16,7 +16,7 @@ from typing import Union from pysimpleini import KeyNotFoundError, SectionNotFoundError -from .core import GameOptions_Factory_Register, OptionType +from .core_gamecfg import GameOptions_Factory_Register, OptionType from .tool_ini import PySimpleINI_GroupKeysInSection from .common_ut import GameOption_UT diff --git a/src/pygamecfg/py.typed b/src/pygamecfg/py.typed new file mode 100644 index 0000000..807db6d --- /dev/null +++ b/src/pygamecfg/py.typed @@ -0,0 +1 @@ +# PlaceHolder \ No newline at end of file -- 2.47.3 From 04fbd77a974106b9dddebe963f6b6f2651199264 Mon Sep 17 00:00:00 2001 From: cclecle Date: Sun, 24 Sep 2023 17:21:48 +0100 Subject: [PATCH 12/13] fix doc gen --- helpers/doc_gen.py | 10 +++++----- helpers/helper_base.py | 6 ++++++ helpers/quality_check.py | 2 +- mkdocs.yml | 2 ++ 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/helpers/doc_gen.py b/helpers/doc_gen.py index 232278e..7c86dd9 100644 --- a/helpers/doc_gen.py +++ b/helpers/doc_gen.py @@ -50,6 +50,7 @@ class doc_gen(helper_withresults_base): reference_path = doc_path / "reference" cls._reset_dir(reference_path) + # create one .md per python module for path in sorted((cls.project_rootdir_path / "src").rglob("*.py")): module_path = path.relative_to(cls.project_rootdir_path / "src").with_suffix("") doc_path = path.relative_to(cls.project_rootdir_path / "src").with_suffix(".md") @@ -57,14 +58,12 @@ class doc_gen(helper_withresults_base): parts = list(module_path.parts) - if parts[-1] == "__init__": - parts = parts[:-1] - elif parts[-1] == "__main__": + if parts[-1] in ("__init__", "__main__"): continue - cls._reset_dir(os.path.dirname(full_doc_path)) + cls._create_dir(full_doc_path.parent.resolve()) with open(full_doc_path, "w+") as fd: - identifier = "src." + ".".join(parts) + identifier = ".".join(parts) print("::: " + identifier, file=fd) cmdopts = [f"{sys.executable}", "-m", "mkdocs", "-v", "build", "--site-dir", str(site_path), "--clean"] @@ -93,6 +92,7 @@ class doc_gen(helper_withresults_base): with open(cls.project_rootdir_path / "mkdocs.yml", "w") as mkdocsCfgFile: mkdocsCfgFile.write(yaml.dump(mkdocsCfg, Dumper=Dumper, default_flow_style=False, sort_keys=False)) + print(" !! start doc generation") res = cls.run_cmd(cmdopts) print(res.decode()) print(" !! done") diff --git a/helpers/helper_base.py b/helpers/helper_base.py index ab88e6a..51829fc 100644 --- a/helpers/helper_base.py +++ b/helpers/helper_base.py @@ -33,6 +33,12 @@ class helper_base(ABC): def get_result_dir(cls): return None + @staticmethod + def _create_dir(dirpath: Path): + dirpath = Path(dirpath) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + @staticmethod def _reset_dir(dirpath: Path): dirpath = Path(dirpath) diff --git a/helpers/quality_check.py b/helpers/quality_check.py index 4028148..c63207b 100644 --- a/helpers/quality_check.py +++ b/helpers/quality_check.py @@ -63,7 +63,7 @@ class quality_check(helper_withresults_base): [ "--load-plugins=pylint.extensions.mccabe", "--output-format=json,parseable", - "--disable=invalid-name", + "--disable=invalid-name,too-few-public-methods,too-many-arguments", # ignore "--ignore=_version.py", "--reports=y", "--score=yes", diff --git a/mkdocs.yml b/mkdocs.yml index 335b00f..b0bd4c5 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,6 +44,8 @@ plugins: default_handler: python handlers: python: + path: + - src options: filters: - '!^_[^_]' -- 2.47.3 From 925010218470fc348ee9e2ace11b2bc31cec8e1f Mon Sep 17 00:00:00 2001 From: cclecle Date: Sun, 24 Sep 2023 18:47:47 +0100 Subject: [PATCH 13/13] fix mkdoc gen :) --- helpers/helper_base.py | 7 +++-- mkdocs.yml | 10 ++++--- pyproject.toml | 1 + pyproject.toml.bak | 68 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 pyproject.toml.bak diff --git a/helpers/helper_base.py b/helpers/helper_base.py index 51829fc..e1bf5db 100644 --- a/helpers/helper_base.py +++ b/helpers/helper_base.py @@ -11,6 +11,7 @@ from typing import TYPE_CHECKING from abc import ABC, abstractmethod import os +import shutil from pathlib import Path import subprocess @@ -42,9 +43,9 @@ class helper_base(ABC): @staticmethod def _reset_dir(dirpath: Path): dirpath = Path(dirpath) - if not os.path.exists(dirpath): - os.makedirs(dirpath) - [f.unlink() for f in Path(dirpath).glob("*") if f.is_file()] + if os.path.exists(dirpath): + shutil.rmtree(dirpath) + os.makedirs(dirpath) @classmethod def reset_result_dir(cls): diff --git a/mkdocs.yml b/mkdocs.yml index b0bd4c5..a3011c8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -14,11 +14,11 @@ theme: - navigation.tabs - navigation.tabs.sticky - navigation.footer - - toc.integrate + - navigation.path - navigation.top - navigation.section - content.code.annotate - - navigation.prune + - navigation.expand - toc.follow palette: - media: '(prefers-color-scheme: dark)' @@ -49,16 +49,18 @@ plugins: options: filters: - '!^_[^_]' - inherited_members: true + inherited_members: false show_if_no_docstring: true show_signature_annotations: true show_source: false show_category_heading: true group_by_category: true - docstring_section_style: spacy show_root_full_path: false merge_init_into_class: true separate_signature: true + heading_level: 2 + docstring_section_style: spacy + show_root_toc_entry: false - with-pdf: cover_subtitle: User Manual cover_logo: C:\Users\chacha\git\pygamecfg\docs-static\Library.jpg diff --git a/pyproject.toml b/pyproject.toml index 1fb8af4..d430840 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ where = ["src"] [tool.setuptools.package-data] "pygamecfg.data" = ["*.*"] +"pysimpleini" = ["py.typed"] [project.urls] Homepage = "https://chacha.ddns.net/gitea/chacha/pygamecfg" diff --git a/pyproject.toml.bak b/pyproject.toml.bak new file mode 100644 index 0000000..1fb8af4 --- /dev/null +++ b/pyproject.toml.bak @@ -0,0 +1,68 @@ +# pyChaChaDummyProject (c) by chacha +# +# pyChaChaDummyProject 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 . + +[build-system] +requires = ["setuptools>=63", "wheel", "setuptools_scm"] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] +version_scheme= "post-release" + +[project] +name = "pygamecfg" +description = "A simple game config tool that provide bash API to read / write game config files (cod, ut ...)" +readme = "README.md" +requires-python = ">=3.9" +keywords = ["chacha","chacha","template","pygamecfg"] +license = { file = "LICENSE.md" } + +authors = [ + {name="chacha",email="1000CHACHA0001@gmail.com"}, +] +maintainers = [ + {name="chacha",email="1000CHACHA0001@gmail.com"}, +] +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.9", +] +dependencies = [ + 'importlib-metadata; python_version<"3.9"', + 'packaging', + 'pysimpleini>=0.3.1', + 'typed-argument-parser==1.*' +] +dynamic = ["version"] + +[tool.setuptools] +platforms = ["any"] +include-package-data = true + +[tool.setuptools.packages.find] +where = ["src"] + +[tool.setuptools.package-data] +"pygamecfg.data" = ["*.*"] + +[project.urls] +Homepage = "https://chacha.ddns.net/gitea/chacha/pygamecfg" +Documentation = "https://chacha.ddns.net/mkdocs-web/chacha/pygamecfg/master/latest/" +Tracker = "https://chacha.ddns.net/gitea/chacha/pygamecfg/issues" + +[project.optional-dependencies] +test = ["junitparser>=2.8","junit2html>=30.1","xmlrunner>=1.7","mypy>=0.99" ] +coverage-check = ["coverage>=7.0"] +complexity-check = ["radon>=5.1"] +quality-check = ["pylint>=2.15","pylint-json2html>=0.4","pandas>=1.5"] +type-check = ["mypy[reports]>=0.99" ] +doc-gen = ["mkdocs>=1.4.0", "mkdocs-material>=8.5","mkdocs-material-extensions","mkdocs-pymdownx-material-extras", "mkdocs-localsearch>=0.9.0", "mkdocstrings[python]>=0.19", "mkdocs-with-pdf>=0.9.3","pyyaml>=6.0","pymdown-extensions>=9","mkdocs-markdownextradata-plugin","mkdocs-mermaid2-plugin","mkdocs-autorefs"] + +# [project.scripts] +# my-script = "my_package.module:function" + -- 2.47.3