learning-website-framework1-5 Exercise 2: Resolve a Link From Any Site =========================================================================== In the content, links are written as root-relative paths of the old single site (for example /linux/shell-and-scripting/vim/). On a multi-site build, a link must stay relative when the target is on the same site and become an absolute URL when the target is on another site. Save the full site map as sitemap.py (the same map as Learning Website: Framework & Architecture 2, plus the one special prefix for the kanji resources): SITES = { "languages": ["france", "germany", "hungary", "japan", "culture"], "webdevelopment": ["web-development", "web-platforms", "web-servers"], "programming": ["programming", "software-development", "maths-for-programmers", "retro-computing", "game-development", "android-development", "ios-development", "databases", "blockchain-and-web3", "developer-tools", "projects"], "systems": ["linux", "operating-systems", "networking", "cloud-and-devops", "security", "technical-support", "raspberry-pi", "windows"], "ai": ["ai", "data-science-and-ml"], "humanities": ["history", "politics", "philosophy", "art-history", "classic-literature", "science-fiction", "sci-fi-project", "music", "football", "ethics", "study-methodologies", "science"], "lifeskills": ["practical-life-skills", "cookery", "freelancing-and-business-skills", "youtube"], "creative": ["creative-and-design-tools", "audio-video-production", "office-and-productivity-software"], } SIDEBAR_ROUTES = { "ai": "ai", "drinks": "lifeskills", "football": "humanities", "history": "humanities", "linux": "systems", "programming": "programming", "web-development": "webdevelopment", } # URLs that are not under a content/ folder name (see Learning Website: Framework & Architecture 2) SPECIAL_PREFIXES = {"resources/japanese": "languages"} FOLDER_TO_SITE = {f: s for s, fs in SITES.items() for f in fs} def site_for(path): path = path.strip("/") for prefix, site in SPECIAL_PREFIXES.items(): if path == prefix or path.startswith(prefix + "/"): return site parts = path.split("/") if parts[0] == "sidebar": return SIDEBAR_ROUTES[parts[1]] return FOLDER_TO_SITE[parts[0]] Save the resolver as links.py: from urllib.parse import urlsplit, urlunsplit from sitemap import site_for BASE = {"production": "https://{site}.osztromok.com", "dev": "http://{site}.localhost:4321"} def link_for(current_site, href, env="production"): """Turn an old-style site-root path into the right link from current_site. - external links and in-page anchors are left alone - same-site targets stay relative (root-relative, trailing slash kept) - other-site targets become absolute URLs on that site """ parts = urlsplit(href) if parts.scheme or parts.netloc or not href.startswith("/"): return href target = site_for(parts.path) if target == current_site: return href base = BASE[env].format(site=target) return urlunsplit((urlsplit(base).scheme, urlsplit(base).netloc, parts.path, parts.query, parts.fragment)) if __name__ == "__main__": tests = [ ("languages", "/japan/hiragana-1/"), ("languages", "/linux/shell-and-scripting/vim/"), ("systems", "/linux/shell-and-scripting/vim/#modes"), ("languages", "/resources/japanese/kanji/kanji_%E4%B8%8A.html"), ("systems", "/sidebar/linux/"), ("languages", "/sidebar/football/"), ("languages", "https://example.org/page/"), ("languages", "#top"), ("systems", "../relative/page.html"), ] for cur, href in tests: print(f"{cur:<10} {href:<52} -> {link_for(cur, href)}") print() print("dev:", link_for("languages", "/linux/", env="dev")) Run it: python links.py Output (checked by running it): languages /japan/hiragana-1/ -> /japan/hiragana-1/ languages /linux/shell-and-scripting/vim/ -> https://systems.osztromok.com/linux/shell-and-scripting/vim/ systems /linux/shell-and-scripting/vim/#modes -> /linux/shell-and-scripting/vim/#modes languages /resources/japanese/kanji/kanji_%E4%B8%8A.html -> /resources/japanese/kanji/kanji_%E4%B8%8A.html systems /sidebar/linux/ -> /sidebar/linux/ languages /sidebar/football/ -> https://humanities.osztromok.com/sidebar/football/ languages https://example.org/page/ -> https://example.org/page/ languages #top -> #top systems ../relative/page.html -> ../relative/page.html dev: http://systems.localhost:4321/linux/ Design points ------------- - External links, in-page anchors (#top) and relative paths (../x.html) are never touched. Only root-relative paths are resolved. - The path, query and fragment are preserved exactly, including the trailing slash that the site's Astro config requires (trailingSlash: 'always'). - The base address per environment is in one table, so local development can use *.localhost while production uses osztromok.com. - A path that belongs to no site raises a KeyError on purpose: a broken link should fail the build, not be shipped. - The kanji resource pages are served from /resources/japanese/..., not from a content folder name, so they need an explicit entry in SPECIAL_PREFIXES. WHY THIS WORKS AS AN ANSWER --------------------------- Content authors can keep writing simple links, and the build decides what is local and what is cross-site. If a folder moves to another site, one line of the map changes and every link follows.