Skip to content

Commit 083a44e

Browse files
committed
add breadcrumb feature for path to recent redfetch binary
1 parent 0ffae2f commit 083a44e

4 files changed

Lines changed: 114 additions & 16 deletions

File tree

src/redfetch/config.py

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
# standard
2+
import json
23
import os
4+
import platform
35
import re
6+
import shutil
47

58
# third-party
69
import tomlkit
710
from dynaconf import Dynaconf, Validator, ValidationError
11+
from platformdirs import user_config_dir, user_data_dir
812

913
# Parent Category to folder
1014
CATEGORY_MAP = {
@@ -51,6 +55,17 @@
5155
"2675": "lootnscoot",
5256
}
5357

58+
BREADCRUMB_FILENAME = "last_command.json"
59+
DEFAULT_CONFIG_DIR = user_config_dir("redfetch", "RedGuides")
60+
61+
script_dir = os.path.dirname(os.path.abspath(__file__))
62+
os.environ['REDFETCH_SCRIPT_DIR'] = script_dir
63+
64+
# Populated by initialize_config()
65+
config_dir = None
66+
env_file_path = None
67+
settings = None
68+
5469

5570
def validate_no_eqgame(path):
5671
"""Validate that the path and its parents don't contain eqgame.exe."""
@@ -94,8 +109,8 @@ def normalize_category_paths(data):
94109
return data
95110

96111

97-
# Custom Dynaconf validator specifically for SPECIAL_RESOURCE paths
98112
def normalize_paths_in_dict(data, parent_key=None):
113+
"""Dynaconf validator for SPECIAL_RESOURCE paths."""
99114
if isinstance(data, dict):
100115
for key, value in data.items():
101116
if isinstance(value, dict):
@@ -115,16 +130,6 @@ def normalize_paths_in_dict(data, parent_key=None):
115130
return data
116131

117132

118-
# Initialize variables
119-
script_dir = os.path.dirname(os.path.abspath(__file__))
120-
os.environ['REDFETCH_SCRIPT_DIR'] = script_dir
121-
122-
# Declare these variables; they will be initialized in initialize_config()
123-
config_dir = None
124-
env_file_path = None
125-
settings = None
126-
127-
128133
def initialize_config():
129134
"""Initialize configuration settings."""
130135
from redfetch.config_firstrun import first_run_setup
@@ -136,9 +141,7 @@ def initialize_config():
136141
os.environ['REDFETCH_CONFIG_DIR'] = config_dir
137142

138143
# Data dir: Linux default uses XDG data dir (~/.local/share), else same as config
139-
import platform
140-
from platformdirs import user_config_dir, user_data_dir
141-
is_linux_default = platform.system() == "Linux" and config_dir == user_config_dir("redfetch", "RedGuides")
144+
is_linux_default = platform.system() == "Linux" and config_dir == DEFAULT_CONFIG_DIR
142145
data_dir = user_data_dir("redfetch", "RedGuides") if is_linux_default else config_dir
143146
os.makedirs(data_dir, exist_ok=True)
144147
os.environ['REDFETCH_DATA_DIR'] = data_dir
@@ -177,10 +180,48 @@ def initialize_config():
177180
]
178181
)
179182

183+
write_breadcrumb()
184+
180185
# Return the settings object for potential use
181186
return settings
182187

183188

189+
def _resolve_redfetch_executable():
190+
"""PYAPP will give a path when built with PYAPP_PASS_LOCATION=1"""
191+
pyapp = os.environ.get("PYAPP")
192+
if pyapp and "redfetch" in os.path.basename(pyapp).lower() and os.path.exists(pyapp):
193+
return os.path.abspath(pyapp)
194+
195+
cmd = shutil.which("redfetch")
196+
if cmd:
197+
return os.path.abspath(cmd)
198+
199+
return None
200+
201+
202+
def write_breadcrumb() -> None:
203+
"""A breadcrumb in the user config dir to track the most recently used redfetch binary's location."""
204+
try:
205+
program = _resolve_redfetch_executable()
206+
if program is None:
207+
return
208+
209+
os.makedirs(DEFAULT_CONFIG_DIR, exist_ok=True)
210+
breadcrumb_path = os.path.join(DEFAULT_CONFIG_DIR, BREADCRUMB_FILENAME)
211+
with open(breadcrumb_path, "w", encoding="utf-8") as f:
212+
json.dump({"program": program}, f)
213+
except Exception:
214+
pass
215+
216+
217+
def remove_breadcrumb() -> None:
218+
breadcrumb_path = os.path.join(DEFAULT_CONFIG_DIR, BREADCRUMB_FILENAME)
219+
try:
220+
os.remove(breadcrumb_path)
221+
except FileNotFoundError:
222+
pass
223+
224+
184225
def switch_environment(new_env):
185226
"""Switch the environment and update the settings."""
186227
if settings is None:

src/redfetch/meta.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,8 @@ def uninstall():
365365
# Call the logout function to clear stored credentials
366366
logout()
367367

368+
config.remove_breadcrumb()
369+
368370
# Get executable path and installation method
369371
executable_path = get_executable_path()
370372
install_method = detect_installation_method()

src/redfetch/sync_types.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,7 @@ class SyncModel(BaseModel):
109109

110110

111111
class TargetIdentity(SyncModel):
112-
"""The same resource_id can appear as both a root and a dependency target
113-
(or under multiple parents), each with its own target_key and planning."""
112+
"""Which target is this? Identifies a single install target"""
114113

115114
target_key: str
116115
resource_id: str

tests/test_breadcrumb.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
"""Tests for the last_command.json breadcrumb that lets MacroQuest discover redfetch."""
2+
import os
3+
4+
import pytest
5+
6+
from redfetch import config
7+
8+
9+
@pytest.fixture
10+
def breadcrumb_dir(tmp_path, monkeypatch):
11+
monkeypatch.setattr(config, "DEFAULT_CONFIG_DIR", str(tmp_path))
12+
return tmp_path
13+
14+
15+
@pytest.fixture(autouse=True)
16+
def _no_pyapp(monkeypatch):
17+
monkeypatch.delenv("PYAPP", raising=False)
18+
19+
20+
def test_ignores_pyapp_pointing_at_wrong_binary(breadcrumb_dir, tmp_path, monkeypatch):
21+
hatch_exe = tmp_path / "hatch.exe"
22+
hatch_exe.write_text("")
23+
monkeypatch.setenv("PYAPP", str(hatch_exe))
24+
25+
def fake_which(cmd):
26+
return None
27+
28+
monkeypatch.setattr(config.shutil, "which", fake_which)
29+
30+
config.write_breadcrumb()
31+
32+
assert not (breadcrumb_dir / config.BREADCRUMB_FILENAME).exists()
33+
34+
35+
def test_skips_write_when_only_sys_executable(breadcrumb_dir, monkeypatch):
36+
def fake_which(cmd):
37+
return None
38+
39+
monkeypatch.setattr(config.shutil, "which", fake_which)
40+
41+
config.write_breadcrumb()
42+
43+
assert not (breadcrumb_dir / config.BREADCRUMB_FILENAME).exists()
44+
45+
46+
def test_write_is_best_effort(breadcrumb_dir, monkeypatch):
47+
def fake_which(cmd):
48+
return "/usr/bin/redfetch"
49+
50+
monkeypatch.setattr(config.shutil, "which", fake_which)
51+
52+
def fail(*args, **kwargs):
53+
raise OSError("disk full")
54+
55+
monkeypatch.setattr(config.os, "makedirs", fail)
56+
config.write_breadcrumb()

0 commit comments

Comments
 (0)