diff --git a/src/install/filesize.py b/src/install/filesize.py
index de92f8c..b18ef68 100644
--- a/src/install/filesize.py
+++ b/src/install/filesize.py
@@ -8,7 +8,7 @@
# Copyright (C) Martijn Faassen, Startifact
#
-from ._types import SizeSystem
+from ..typings import SizeSystem
traditional: SizeSystem = [
(1024 ** 5, 'P'),
diff --git a/src/install/loadingbar.py b/src/install/loadingbar.py
index e39b479..1097eea 100644
--- a/src/install/loadingbar.py
+++ b/src/install/loadingbar.py
@@ -15,20 +15,66 @@
# along with this program. If not, see .
from os import get_terminal_size
-from typing import Collection, Generic, Literal, TypeVar, Optional, Type
-from types import TracebackType
+from typing import (
+ Collection, Generic, Literal, TypeVar,
+ Optional, overload, TYPE_CHECKING
+)
+
+if TYPE_CHECKING:
+ from types import TracebackType
from .filesize import size, traditional
-T = TypeVar("T")
+_T = TypeVar("_T")
+
+
+class loadingbar(Generic[_T]):
+ @overload
+ def __init__(
+ # total
+ self, iterator: Collection[_T], /, *,
+ unit: Literal['it', 'B'] = 'it',
+ bar_length: Optional[int] = None,
+ bar_format: str = "{title} {percentage:3.0f}% [{bar}] {current}/{total}",
+ title: str = '',
+ show_desc: bool = False,
+ desc: str = '',
+ disappear: bool = False,
+ ) -> None: ...
+ @overload
+ def __init__(
+ # total
+ self, /, *,
+ total: _T,
+ unit: Literal['it', 'B'] = 'it',
+ bar_length: Optional[int] = None,
+ bar_format: str = "{title} {percentage:3.0f}% [{bar}] {current}/{total}",
+ title: str = '',
+ show_desc: bool = False,
+ desc: str = '',
+ disappear: bool = False,
+ ) -> None: ...
-class loadingbar(Generic[T]):
+ @overload
def __init__(
- self, iterator: Collection[T] | None = None, /, *,
+ # both
+ self, iterator: Optional[Collection[_T]] = None, /, *,
total: int = 0,
unit: Literal['it', 'B'] = 'it',
- bar_length: int | None = None,
+ bar_length: Optional[int] = None,
+ bar_format: str = "{title} {percentage:3.0f}% [{bar}] {current}/{total}",
+ title: str = '',
+ show_desc: bool = False,
+ desc: str = '',
+ disappear: bool = False,
+ ) -> None: ...
+
+ def __init__(
+ self, iterator: Optional[Collection[_T]] = None, /, *,
+ total: int | _T = 0,
+ unit: Literal['it', 'B'] = 'it',
+ bar_length: Optional[int] = None,
bar_format: str = "{title} {percentage:3.0f}% [{bar}] {current}/{total}",
title: str = '',
show_desc: bool = False,
@@ -54,21 +100,18 @@ def __init__(
:param disappear: If you want the bar to disappear
"""
- # Define class variables
- self._iter_type: Literal['iter', 'total', 'both']
-
if iterator is not None and total == 0:
- self._iter_type = 'iter'
+ # iter
self.iterator = iterator
self.iterable = iter(iterator)
self.iterator_len = len(iterator)
elif iterator is None and total != 0:
- self._iter_type = 'total'
+ # total
self.iterator = None
self.iterable = None
self.total = total
elif iterator is not None and total != 0:
- self._iter_type = 'both'
+ # both
self.iterator = iterator
self.iterable = iter(iterator)
self.iterator_len = len(iterator)
@@ -112,40 +155,44 @@ def __init__(
else:
self.bar_length = bar_length
- def __iter__(self) -> "loadingbar[T]":
+ def __iter__(self) -> "loadingbar[_T]":
"Method that allows iterators"
return self
- def __next__(self) -> T:
+ def __next__(self) -> _T:
"Go to the next iteration"
# Update the item and idx
if self.iterable is not None:
self.item = next(self.iterable)
self.idx += 1
- elif self.idx < self.total: # 'total'
- self.idx += 1
else:
- raise StopIteration
+ if TYPE_CHECKING and not isinstance(self.total, int):
+ raise TypeError
+
+ if self.idx < self.total: # total
+ self.idx += 1
+ else:
+ raise StopIteration
# Refresh the loading bar
self.refresh()
# Return the item
- if self.iterator is not None: # 'both' or 'iter'
+ if self.iterator is not None: # both or iter
return self.item
else:
return self.idx # type: ignore
- def __enter__(self) -> "loadingbar[T]":
+ def __enter__(self) -> "loadingbar[_T]":
"Allow a with-as statement"
return self
- def __exit__(self, exc_type: Optional[Type[BaseException]],
+ def __exit__(self, exc_type: Optional[type[BaseException]],
exc_value: Optional[BaseException],
- traceback: Optional[TracebackType]) -> bool:
+ traceback: Optional['TracebackType']) -> bool:
"Allow a with-as statement"
return False # No errors
@@ -154,7 +201,10 @@ def refresh(self) -> None:
"Refresh the loading bar, called automatically"
# Calculate progress
- if self._iter_type in ('both', 'total'):
+ if hasattr(self, 'total'): # both, total
+ if TYPE_CHECKING and not isinstance(self.total, int):
+ raise TypeError
+
percent = round(self.idx / self.total * 100, 0)
else:
percent = round(self.idx / self.iterator_len * 100, 0)
@@ -165,12 +215,16 @@ def refresh(self) -> None:
# Define the current and total
if self.unit == 'it':
current = str(self.idx)
- total = str(self.total if self._iter_type in (
- 'both', 'total') else self.iterator_len)
+ total = str(self.total if hasattr(
+ self, 'total') else self.iterator_len)
else:
current = size(self.idx, traditional)
- total = size(self.total if self._iter_type in (
- 'both', 'total') else self.iterator_len, traditional)
+ arg = self.total if hasattr(self, 'total') else self.iterator_len
+
+ if TYPE_CHECKING and not isinstance(arg, int):
+ raise TypeError
+
+ total = size(arg, traditional)
# Define the text length
text_length = self.formatting_length + \
@@ -181,12 +235,10 @@ def refresh(self) -> None:
# Print the loading bar
start = '\033[F\r' if self.show_desc else '\r'
- if self.show_desc and self._new_desc:
- end = '\n\033[K' + self.desc
- elif self.show_desc:
- end = '\n' + self.desc
- else:
- end = ''
+ end = (
+ '\n\033[K' + self.desc if self.show_desc and self._new_desc
+ else '\n' + self.desc if self.show_desc else ''
+ )
print(start + self.bar_format.format(
title=self.title,
@@ -197,8 +249,7 @@ def refresh(self) -> None:
), end=end)
# Clear the loading bar at the end
- if self.idx == (self.total if self._iter_type in (
- 'both', 'total') else self.iterator_len):
+ if self.idx == (self.total if hasattr(self, 'total') else self.iterator_len):
if self.disappear and self.show_desc:
print('\r\033[K\033[F\r\033[K', end='')
elif self.disappear:
@@ -208,7 +259,7 @@ def refresh(self) -> None:
def update(self, amount: int) -> None:
"Add 'n' amount of iterations to the loading bar"
- if self._iter_type == 'iter':
+ if not hasattr(self, 'total'):
i = 0
# Call next(self) while less than amount
diff --git a/src/install/media.py b/src/install/media.py
index abf6a23..c74e71a 100644
--- a/src/install/media.py
+++ b/src/install/media.py
@@ -24,7 +24,7 @@
from .modloaders import inst_modloader, MINECRAFT_DIR
from .filesize import size, alternative
-from ._types import Manifest, URLMedia, Media, MediaList, Side
+from ..typings import Manifest, URLMedia, Media, MediaList, Side
class prepare:
@@ -218,7 +218,6 @@ def download_files(total_size: int, install_path: str, side: Side, manifest: Man
show_desc=True,
disappear=True
) as bar:
- bar: loadingbar[int] # The only way it worked out
for url, fname, fsize, sides in iterator:
if side not in sides:
# As the size isn't calculated, it
@@ -307,7 +306,8 @@ def install(
# Print the mod info
print(
- f"\n{len(mods)} mods, {len(resourcepacks)} recourcepacks, {len(shaderpacks)} shaderpacks\n"
+ f"\n{len(mods)} mods, {len(resourcepacks)} "
+ f"recourcepacks, {len(shaderpacks)} shaderpacks\n"
f"Total file size: {size(total_size, system=alternative)}"
)
diff --git a/src/install/modloaders.py b/src/install/modloaders.py
index e081e31..f10fc16 100644
--- a/src/install/modloaders.py
+++ b/src/install/modloaders.py
@@ -15,6 +15,7 @@
# along with this program. If not, see .
from atexit import register
+from http.client import HTTPResponse
from json import load, loads, dump
from os import path, getenv, mkdir, rename
from shutil import rmtree, copyfile
@@ -24,16 +25,16 @@
from urllib import request
from zipfile import ZipFile
-from .urls import forge as forge_urls, fabric as fabric_urls
from .loadingbar import loadingbar
+from .urls import forge as forge_urls
from ..common.maven_coords import maven_parse
-from ._types import (
+from ..typings import (
Client, Server, Side, Modloader,
- MinecraftJson, VersionJson,
+ ForgeLibrary, ForgeVersionJson,
InstallProfile, Libraries,
- Library, OSLibrary
+ MinecraftJson, OSLibrary
)
# Define the minecraft directory
@@ -105,13 +106,14 @@ def __init__(self, mc_version: str,
"Please make sure you have Java installed and it is properly configured."
)
- self.minecraft_json: MinecraftJson = loads(request.urlopen(
- [item for item in loads(
- request.urlopen(
- forge_urls.version_manifest_v2
- ).read().decode('utf-8')
- )['versions'] if item['id'] == mc_version][0]['url']
- ).read().decode('utf-8'))
+ v_man_v2: HTTPResponse = request.urlopen(
+ forge_urls.version_manifest_v2)
+
+ for item in loads(v_man_v2.read().decode('utf-8'))['versions']:
+ if item['id'] == mc_version:
+ _res: HTTPResponse = request.urlopen(item['url'])
+ self.minecraft_json: MinecraftJson = loads(
+ _res.read().decode('utf-8'))
# Exit if the launcher hasn't launched once
if not path.isfile(path.join(launcher_dir, 'launcher_profiles.json')) and side == 'client':
@@ -127,7 +129,7 @@ def __init__(self, mc_version: str,
with archive.open('install_profile.json') as fp:
self.install_profile: InstallProfile = load(fp)
with archive.open('version.json') as fp:
- self.version_json: VersionJson = load(fp)
+ self.version_json: ForgeVersionJson = load(fp)
# Define the forge dir
forge_dir = path.join(
@@ -203,8 +205,10 @@ def download_jar_files(self) -> None:
if self.side == 'client':
# Make the required directories
- for directory in [path.join(self.launcher_dir, 'versions'),
- path.join(self.launcher_dir, 'versions', self.mc_version)]:
+ for directory in [
+ path.join(self.launcher_dir, 'versions'),
+ path.join(self.launcher_dir, 'versions', self.mc_version)
+ ]:
if not path.isdir(directory):
mkdir(directory)
@@ -227,7 +231,7 @@ def download_jar_files(self) -> None:
break
mod_file.write(resp_data)
- def download_library(self, bar: loadingbar[Library | OSLibrary], library: Library) -> None:
+ def download_library(self, bar: loadingbar[ForgeLibrary | OSLibrary], library: ForgeLibrary) -> None:
"""Download a library"""
# Define the java os names
osdict = {
@@ -384,33 +388,7 @@ def build_processors(self) -> None:
class fabric:
- "I accidentally uploaded this, it's unfinished"
-
- def __init__(self, mc_version: str,
- fabric_version: str,
- side: Side,
- install_dir: str = MINECRAFT_DIR,
- launcher_dir: str = MINECRAFT_DIR) -> None:
-
- self.mc_version = mc_version
-
- # Define the class variables
- launcher_dir = install_dir if side == 'server' else launcher_dir
-
- self.mc_version = mc_version
- self.fabric_version = fabric_version
- self.side = side
- self.install_dir = install_dir
- self.launcher_dir = launcher_dir
-
- self.install_version()
-
- def install_version(self):
- print(request.urlopen(fabric_urls.api_url(
- 'v2', 'versions', 'loader',
- self.mc_version, self.fabric_version,
- 'server', 'json'
- )).read().decode('utf-8'))
+ pass
def inst_modloader(
diff --git a/src/install/urls.py b/src/install/urls.py
index b669b35..b4d5828 100644
--- a/src/install/urls.py
+++ b/src/install/urls.py
@@ -14,7 +14,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-from ._types import Media
+from ..typings import Media
def media_url(media: Media) -> str:
@@ -44,9 +44,3 @@ class forge:
def forge_installer_url(mc_version: str, forge_version: str) -> str:
return "https://maven.minecraftforge.net/net/minecraftforge/forge/" \
f"{mc_version}-{forge_version}/forge-{mc_version}-{forge_version}-installer.jar"
-
-
-class fabric:
- @staticmethod
- def api_url(*paths: str) -> str:
- return "https://meta.fabricmc.net/" + '/'.join(paths)
diff --git a/src/runner.py b/src/runner.py
index 324e50e..54408cc 100644
--- a/src/runner.py
+++ b/src/runner.py
@@ -21,7 +21,7 @@
from .install.media import install
from .install.modloaders import MINECRAFT_DIR
-from .install._types import Side
+from .typings import Side
class Options(TypedDict):
diff --git a/src/install/_types.py b/src/typings.py
similarity index 67%
rename from src/install/_types.py
rename to src/typings.py
index 155883f..907c2eb 100644
--- a/src/install/_types.py
+++ b/src/typings.py
@@ -19,11 +19,15 @@
Generic, NotRequired, Optional
)
-# --- filesize.py --- #
+# ============================ #
+# install/filesize.py #
+# ============================ #
SizeSystem = list[tuple[int, str | tuple[str, str]]]
-# --- common --- #
+# ============================ #
+# install/ #
+# ============================ #
Client = Literal['client']
Server = Literal['server']
Side = Literal['client', 'server']
@@ -31,12 +35,12 @@
_T = TypeVar("_T", bound=Literal['cf', 'pm', 'mr', 'url'])
-class Minecraft(TypedDict):
+class _Minecraft(TypedDict):
version: str
modloader: str
-class Info(TypedDict, total=False):
+class _Info(TypedDict, total=False):
"Dictionary with info about media"
title: str
icon: str
@@ -49,7 +53,7 @@ class _Media(Generic[_T], TypedDict):
slug: str
name: str
sides: list[Literal['client', 'server']]
- info: NotRequired[Info]
+ info: NotRequired[_Info]
# Download info: url, path, size
_dl: NotRequired[tuple[str, str, int]]
@@ -82,23 +86,30 @@ class URLMedia(_Media[Literal['url']]):
class Manifest(TypedDict):
"All information of the modpack"
- minecraft: Minecraft
+ minecraft: _Minecraft
mods: MediaList
resourcepacks: MediaList
shaderpacks: MediaList
-# --- modloaders.py --- #
+# ============================ #
+# install/modloaders.py #
+# ============================ #
Modloader = Literal['forge', 'fabric'] | str
+# ========= #
+# Forge #
+# ========= #
+
+# Libraries
class _OSDict(TypedDict):
name: Literal["windows", "linux", "osx"] # NotRequired
arch: NotRequired[Literal["x86"]]
-class Library(TypedDict):
- "All libraries of the media"
+class ForgeLibrary(TypedDict):
+ "A forge library"
path: str
sha1: str
size: int
@@ -111,15 +122,15 @@ class _Rules(TypedDict):
os: _OSDict # NotRequired
-class OSLibrary(Library, _Rules):
- "All libraries of the media for a specific os"
-
+class OSLibrary(ForgeLibrary, _Rules):
+ "A library for a specific OS"
...
-Libraries = dict[str, Library | OSLibrary]
+Libraries = dict[str, ForgeLibrary | OSLibrary]
+# Forge minecraft and version json
class _Arg(TypedDict):
rules: list[_Rules]
value: str | list[str]
@@ -167,12 +178,6 @@ class _Download(TypedDict):
artifact: _Artifact
-class _JavaLibrary(TypedDict):
- downloads: _Download
- name: str
- rules: NotRequired[list[_Rules]]
-
-
class _File(TypedDict):
id: str
sha1: str
@@ -190,10 +195,13 @@ class _Logging(TypedDict):
client: _LoggingClient
-class JavaJson(TypedDict):
+_L = TypeVar('_L')
+
+
+class _JavaJson(TypedDict, Generic[_L]):
arguments: _Arguments
id: str
- libraries: list[_JavaLibrary]
+ libraries: list[_L]
logging: _Logging
mainClass: str
releaseTime: str
@@ -201,7 +209,13 @@ class JavaJson(TypedDict):
type: str
-class MinecraftJson(JavaJson):
+class _ForgeLibrary(TypedDict):
+ downloads: _Download
+ name: str
+ rules: NotRequired[list[_Rules]]
+
+
+class MinecraftJson(_JavaJson[_ForgeLibrary]):
"Minecraft version_manifest.json file"
assetIndex: _AssetIndex
assets: str
@@ -211,12 +225,12 @@ class MinecraftJson(JavaJson):
minimumLauncherVersion: int
-class VersionJson(JavaJson):
- "Minecraft version.json file"
+class ForgeVersionJson(_JavaJson[_ForgeLibrary]):
+ "Forge's minecraft version.json file"
inheritsFrom: str
-# -- Install profile -- #
+# Install profile
class _Data(TypedDict):
client: str
server: str
@@ -238,9 +252,71 @@ class InstallProfile(TypedDict):
serverJarPath: str
data: dict[str, _Data]
processors: list[_Processor]
- libraries: list[_JavaLibrary]
+ libraries: list[_ForgeLibrary]
icon: str
json: str
logo: str
mirrorList: str
welcome: str
+
+
+# ========== #
+# Fabric #
+# ========== #
+
+# Loader
+class _Loader(TypedDict):
+ seperator: str
+ build: int
+ maven: str
+ version: str
+ stable: bool
+
+
+class _Intermediary(TypedDict):
+ maven: str
+ version: str
+ stable: bool
+
+
+class _FabricLibrary(TypedDict):
+ name: str
+ url: str
+
+
+class _FabricLibraries(TypedDict):
+ client: list[_FabricLibrary]
+ common: list[_FabricLibrary]
+ server: list[_FabricLibrary]
+
+
+class _MainClass(TypedDict):
+ client: str
+ server: str
+
+
+class _LauncherMeta(TypedDict):
+ version: int
+ libraries: _FabricLibraries
+ mainClass: _MainClass
+
+
+class LoaderJson(TypedDict):
+ "Information about the fabric loader"
+ loader: _Loader
+ intermediary: _Intermediary
+ launcherMeta: _LauncherMeta
+
+
+class FabricLibrary(_FabricLibrary):
+ "A fabric library"
+ file: str
+
+
+LibraryList = list[FabricLibrary]
+
+
+# Fabric version json
+class FabricVersionJson(_JavaJson[_FabricLibrary]):
+ "Fabric's minecraft version.json file"
+ inheritsFrom: str
diff --git a/tests/install/test_loadingbar.py b/tests/install/test_loadingbar.py
index de38111..365c8d3 100644
--- a/tests/install/test_loadingbar.py
+++ b/tests/install/test_loadingbar.py
@@ -53,7 +53,7 @@ def test_bar(self):
self.assertEqual(bar.desc, "Test description 2")
# Updating with .update() and using total=int
- bar_2: loadingbar[int] = loadingbar(total=100)
+ bar_2 = loadingbar(total=100)
bar_2.update(50)
self.assertEqual(bar_2.idx, 50)
diff --git a/tests/install/test_urls.py b/tests/install/test_urls.py
index 79ae68f..3cc8af9 100644
--- a/tests/install/test_urls.py
+++ b/tests/install/test_urls.py
@@ -17,7 +17,7 @@
import unittest
from src.install.urls import media_url, forge
-from src.install._types import Media
+from src.typings import Media
class Urls(unittest.TestCase):