Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rework Shulker Box Folder Settings #118

Merged
merged 5 commits into from
Nov 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/suggestions.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ start is their priority values):
!!! note
If you're creating your shulker box through the command-line interface,
this is pretty much the only sort of box where I'd recommend selecting
the "Standard" set of linked-folders.
the "Global" set of linked-folders.

This is also where I have a baseline `options.txt` file. It's almost certain
to get replaced in the actual instance, but it saves me so much aggravation
Expand Down Expand Up @@ -199,7 +199,7 @@ From there, you then create a shulker box for each instance that contains
_symlinks_ pointing into the files that live in the EnderChest (_e.g._
`instance_shulker/options.txt -> _Chest Monster/options files/basic_options.txt`).

Each instance will probably want to use the standard set of linked folders so
Each instance will probably want to use the "Global" set of linked folders so
that when an instance generates new screenshots, logs, crash reports, etc., they
go into the EnderChest, and by making the "folders" inside of the shulker boxes
_symlinks themselves_, they can point into either shared or separated folders
Expand Down
25 changes: 16 additions & 9 deletions enderchest/craft.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from .loggers import CRAFT_LOGGER
from .prompt import NO, YES, confirm, prompt
from .remote import fetch_remotes_from_a_remote_ender_chest
from .shulker_box import STANDARD_LINK_FOLDERS, ShulkerBox, create_shulker_box
from .shulker_box import ShulkerBox, create_shulker_box


def craft_ender_chest(
Expand Down Expand Up @@ -183,6 +183,7 @@ def craft_shulker_box(
return

try:
folders = load_ender_chest(minecraft_root).shulker_box_folders
if (
priority is None
and link_folders is None
Expand Down Expand Up @@ -228,7 +229,7 @@ def craft_shulker_box(
CRAFT_LOGGER.error(no_ender_chest)
return

create_shulker_box(minecraft_root, shulker_box)
create_shulker_box(minecraft_root, shulker_box, folders)


def specify_ender_chest_from_prompt(minecraft_root: Path) -> EnderChest:
Expand Down Expand Up @@ -413,6 +414,7 @@ def specify_shulker_box_from_prompt(minecraft_root: Path, name: str) -> ShulkerB
ShulkerBox
The resulting ShulkerBox
"""
ender_chest = load_ender_chest(minecraft_root)
shulker_root = fs.shulker_box_root(minecraft_root, name)
if shulker_root in shulker_root.parent.iterdir():
if not shulker_root.is_dir():
Expand Down Expand Up @@ -468,14 +470,20 @@ def refresh_ender_chest_instance_list() -> Sequence[InstanceSpec]:
while True:
selection_type = prompt(
"Folders to Link?"
"\nUse the [S]tandard set, [M]anually specify or do [N]one?"
"\nThe standard set is: " + ", ".join(STANDARD_LINK_FOLDERS)
"\nThe [G]lobal set is:"
f' {", ".join(ender_chest.global_link_folders) or "(none)"}'
"\nThe [S]tandard set is:"
f' {", ".join(ender_chest.standard_link_folders) or "(none)"}'
"\nYou can also choose [N]one or to [M]anually specify the folders to link",
suggestion="S",
).lower()
match selection_type:
case "n" | "none":
link_folders: tuple[str, ...] = ()
case "s" | "standard" | "standard set":
link_folders = STANDARD_LINK_FOLDERS
case "g" | "global" | "global set":
link_folders = tuple(ender_chest.global_link_folders)
case "s" | "standard" | "standard set" | "":
link_folders = tuple(ender_chest.standard_link_folders)
case "m" | "manual" | "manually specify":
folder_choices = prompt(
"Specify the folders to link using a comma-separated list"
Expand Down Expand Up @@ -522,16 +530,15 @@ def refresh_ender_chest_instance_list() -> Sequence[InstanceSpec]:
"What hosts (EnderChest installations) should use this shulker box?"
"\nProvide a comma-separated list (wildcards are allowed)"
"\nand remember to include the name of this EnderChest"
f' ("{load_ender_chest(minecraft_root).name}")'
f' ("{ender_chest.name}")'
),
suggestion="*",
)
or "*"
)
hosts = tuple(host.strip() for host in values.split(","))

# TODO: stop wastefully reloading the cfg
host = load_ender_chest(minecraft_root).name
host = ender_chest.name

if not shulker_box._replace(match_criteria=(("hosts", hosts),)).matches_host(
host
Expand Down
80 changes: 70 additions & 10 deletions enderchest/enderchest.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,38 @@
from .loggers import CRAFT_LOGGER, GATHER_LOGGER
from .sync import abspath_from_uri

_DEFAULTS = (
("offer_to_update_symlink_allowlist", True),
("sync_confirm_wait", 5),
("place_after_open", True),
("do_not_sync", ("EnderChest/enderchest.cfg", "EnderChest/.*", ".DS_Store")),
(
"shulker_box_folders",
(
"config",
"mods",
"resourcepacks",
"saves",
"shaderpacks",
),
),
("standard_link_folders", ()),
(
"global_link_folders",
(
"backups",
"cachedImages",
"crash-reports",
"logs",
"replay_recordings",
"screenshots",
"schematics",
"config/litematica", # still worth having in case max_depth>2
".bobby",
),
),
)


@dataclass(init=False, repr=False, eq=False)
class EnderChest:
Expand Down Expand Up @@ -78,16 +110,26 @@ class EnderChest:
installations. By default, this list comprises `EnderChest/enderchest.cfg`,
any top-level folders starting with a "." (like .git) and
`.DS_Store` (for all you mac gamers).
shulker_box_folders : list of str
The folders that will be created inside each new shulker box
standard_link_folders : list of str
The default set of "link folders" when crafting a new shulker box
global_link_folders : list of str
The "global" set of "link folders," offered as a suggestion when
crafting a new shulker box
"""

name: str
_uri: ParseResult
_instances: list[i.InstanceSpec]
_remotes: dict[str, ParseResult]
offer_to_update_symlink_allowlist: bool = True
sync_confirm_wait: bool | int = 5
place_after_open: bool = True
do_not_sync = ["EnderChest/enderchest.cfg", "EnderChest/.*", ".DS_Store"]
offer_to_update_symlink_allowlist: bool
sync_confirm_wait: bool | int
place_after_open: bool
do_not_sync: list[str]
shulker_box_folders: list[str]
standard_link_folders: list[str]
global_link_folders: list[str]

def __init__(
self,
Expand All @@ -97,6 +139,9 @@ def __init__(
| None = None,
instances: Iterable[i.InstanceSpec] | None = None,
):
for setting, value in _DEFAULTS:
setattr(self, setting, list(value) if isinstance(value, tuple) else value)

try:
if isinstance(uri, ParseResult):
self._uri = uri
Expand Down Expand Up @@ -255,6 +300,11 @@ def from_cfg(cls, config_file: Path) -> "EnderChest":
place_after_open: bool | None = None
offer_to_update_symlink_allowlist: bool = True
do_not_sync: list[str] | None = None
folder_defaults: dict[str, list[str] | None] = {
"shulker_box_folders": None,
"standard_link_folders": None,
"global_link_folders": None,
}

for section in config.sections():
if section == "properties":
Expand All @@ -270,6 +320,12 @@ def from_cfg(cls, config_file: Path) -> "EnderChest":
do_not_sync = cfg.parse_ini_list(
config[section]["do-not-sync"] or ""
)
for setting in folder_defaults.keys():
setting_key = setting.replace("_", "-")
if setting_key in config[section].keys():
folder_defaults[setting] = cfg.parse_ini_list(
config[section][setting_key] or ""
)
elif section == "remotes":
for remote in config[section].items():
if remote[1] is None:
Expand Down Expand Up @@ -329,6 +385,11 @@ def from_cfg(cls, config_file: Path) -> "EnderChest":
)
ender_chest.do_not_sync.insert(0, chest_cfg_exclusion)
requires_rewrite = True
for setting in folder_defaults:
if folder_defaults[setting] is None:
folder_defaults[setting] = dict(_DEFAULTS)[setting] # type: ignore
# requires_rewrite = True # though I'm considering it
setattr(ender_chest, setting, folder_defaults[setting])

if requires_rewrite:
ender_chest.write_to_cfg(config_file)
Expand Down Expand Up @@ -363,12 +424,11 @@ def write_to_cfg(self, config_file: Path | None = None) -> str:
else:
properties["sync-confirmation-time"] = self.sync_confirm_wait

properties["place-after-open"] = self.place_after_open
properties[
"offer-to-update-symlink-allowlist"
] = self.offer_to_update_symlink_allowlist

properties["do-not-sync"] = self.do_not_sync
for setting, _ in _DEFAULTS:
if setting == "sync_confirm_wait":
continue # already did this one
setting_key = setting.replace("_", "-")
properties[setting_key] = getattr(self, setting)

remotes: dict[str, str] = {name: uri.geturl() for uri, name in self.remotes}

Expand Down
31 changes: 8 additions & 23 deletions enderchest/shulker_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import fnmatch
import os
from pathlib import Path
from typing import Any, NamedTuple
from typing import Any, Iterable, NamedTuple

import semantic_version as semver

Expand Down Expand Up @@ -299,26 +299,9 @@ def _matches_version(version_spec: str, version_string: str) -> bool:
return fnmatch.fnmatchcase(version_string.lower(), version_spec.lower())


DEFAULT_SHULKER_FOLDERS = ( # TODO: customize in enderchest.cfg
"config",
"mods",
"resourcepacks",
"saves",
"shaderpacks",
)

STANDARD_LINK_FOLDERS = ( # TODO: customize in enderchest.cfg
"backups",
"cachedImages",
"crash-reports",
"logs",
"replay_recordings",
"screenshots",
".bobby",
)


def create_shulker_box(minecraft_root: Path, shulker_box: ShulkerBox) -> None:
def create_shulker_box(
minecraft_root: Path, shulker_box: ShulkerBox, folders: Iterable[str]
) -> None:
"""Create a shulker box folder based on the provided configuration

Parameters
Expand All @@ -328,6 +311,8 @@ def create_shulker_box(minecraft_root: Path, shulker_box: ShulkerBox) -> None:
that's the parent of your EnderChest folder)
shulker_box : ShulkerBox
The spec of the box to create
folders : list-like of str
The folders to create inside the shulker box (not including link folders)

Notes
-----
Expand All @@ -337,13 +322,13 @@ def create_shulker_box(minecraft_root: Path, shulker_box: ShulkerBox) -> None:
- This method will fail if there is no EnderChest set up in the minecraft
root
- This method does not check to see if there is already a shulker box
set up at the specificed location--if one exists, its config will
set up at the specified location--if one exists, its config will
be overwritten
"""
root = fs.shulker_box_root(minecraft_root, shulker_box.name)
root.mkdir(exist_ok=True)

for folder in (*DEFAULT_SHULKER_FOLDERS, *shulker_box.link_folders):
for folder in (*folders, *shulker_box.link_folders):
CRAFT_LOGGER.debug(f"Creating {root / folder}")
(root / folder).mkdir(exist_ok=True, parents=True)

Expand Down
20 changes: 11 additions & 9 deletions enderchest/test/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ def test_first_arg_interpreted_as_root(self): # I actually really don't like th
class TestInventory(ActionTestSuite):
action = "inventory"

def test_no_args_routes_to_load_shulker_boxes(self, monkeypatch):
def test_no_args_routes_to_load_shulker_boxes(self, monkeypatch) -> None:
gather_log: list[tuple[str, dict]] = []

def mock_load_shulker_boxes(root, **kwargs) -> None:
Expand All @@ -406,7 +406,7 @@ def mock_load_shulker_boxes(root, **kwargs) -> None:
@pytest.mark.parametrize("action_version", ("short", "long"))
def test_no_path_routes_to_boxes_matching_instance(
self, monkeypatch, action_version
):
) -> None:
gather_log: list[tuple[str, str, dict]] = []

def mock_get_shulker_boxes_matching_instance(root, name, **kwargs) -> None:
Expand Down Expand Up @@ -440,7 +440,7 @@ def test_long_action_requires_instance_name(self):
)
def test_providing_a_path_always_routes_to_list_placements(
self, monkeypatch, instance_how
):
) -> None:
def mock_get_shulker_boxes_matching_instance(*args, **kwargs) -> None:
raise AssertionError("I should not have been called!")

Expand All @@ -461,13 +461,15 @@ def mock_list_placements(root, pattern, **kwargs) -> None:
mock_list_placements,
)

instance = None if instance_how == "no_instance" else "cherry grove"
if instance_how == "no_instance":
instance_args = []
elif instance_how == "long_action":
instance_args = ["instance", instance]
else: # if instance_how == "short_action":
instance_args = ["-i", instance]
instance = None
instance_args: list[str] = []
else:
instance = "cherry grove"
if instance_how == "long_action":
instance_args = ["instance", instance]
else: # if instance_how == "short_action":
instance_args = ["-i", instance]

action, root, _, kwargs = cli.parse_args(
["enderchest", *self.action.split(), *instance_args, "-p", "of_jelly.jar"]
Expand Down
5 changes: 4 additions & 1 deletion enderchest/test/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from enderchest import EnderChest, InstanceSpec, ShulkerBox
from enderchest import config as cfg
from enderchest import filesystem as fs
from enderchest.enderchest import _DEFAULTS
from enderchest.gather import load_shulker_boxes
from enderchest.shulker_box import create_shulker_box
from enderchest.test import utils
Expand Down Expand Up @@ -67,7 +68,9 @@ def test_shulker_box_config_roundtrip(self, minecraft_root):

utils.pre_populate_enderchest(minecraft_root / "EnderChest")

create_shulker_box(minecraft_root, original_shulker)
create_shulker_box(
minecraft_root, original_shulker, dict(_DEFAULTS)["shulker_box_folders"]
)

parsed_boxes = load_shulker_boxes(minecraft_root)

Expand Down
Loading