tux.wrappers.tldr
¶
TLDR Pages Client Wrapper.
A pure Python implementation of the TLDR client specification v2.3, providing command documentation lookup with proper caching, localization, and platform support. This wrapper contains no Discord dependencies and can be used independently.
Classes:
Name | Description |
---|---|
TldrClient | Core TLDR client functionality for fetching and managing pages. |
Classes¶
TldrClient
¶
Core TLDR client functionality for fetching and managing pages.
Implements the TLDR client specification v2.3 with proper caching, platform detection, and language fallback mechanisms.
Methods:
Name | Description |
---|---|
normalize_page_name | Normalize command name according to TLDR specification. |
get_cache_file_path | Generate the file system path for a cached TLDR page. |
have_recent_cache | Check if a recent cached version of a page exists. |
load_page_from_cache | Load a TLDR page from local cache. |
store_page_to_cache | Store a TLDR page to local cache. |
detect_platform | Detect the default platform for Discord bot context. |
get_language_priority | Get prioritized list of languages for Discord bot context. |
get_platform_priority | Determine platform search order based on user input and TLDR spec. |
fetch_tldr_page | Fetch a TLDR page with platform priority and language fallback. |
list_tldr_commands | List available TLDR commands for a given language and platform filter. |
parse_placeholders | Parse and format placeholder text in TLDR pages. |
format_tldr_for_discord | Format a TLDR markdown page for Discord output. |
not_found_message | Generate a message for when a page is not found. |
update_tldr_cache | Update the TLDR cache for a specific language. |
cache_needs_update | Check if the cache needs updating based on age. |
split_long_text | Split long text into pages for Discord embeds. |
Functions¶
normalize_page_name(name: str) -> str
staticmethod
¶
Normalize command name according to TLDR specification.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
name | str | Raw command name that may contain spaces or mixed case. | required |
Returns:
Type | Description |
---|---|
str | Normalized command name: lowercase, dash-separated, trimmed. |
Examples:
>>> TldrClient.normalize_page_name("git status")
"git-status"
>>> TldrClient.normalize_page_name("GyE D3")
"gye-d3"
Source code in tux/wrappers/tldr.py
@staticmethod
def normalize_page_name(name: str) -> str:
"""
Normalize command name according to TLDR specification.
Parameters
----------
name : str
Raw command name that may contain spaces or mixed case.
Returns
-------
str
Normalized command name: lowercase, dash-separated, trimmed.
Examples
--------
>>> TldrClient.normalize_page_name("git status")
"git-status"
>>> TldrClient.normalize_page_name("GyE D3")
"gye-d3"
"""
return "-".join(name.lower().strip().split())
get_cache_file_path(command: str, platform: str, language: str) -> Path
staticmethod
¶
Generate the file system path for a cached TLDR page.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
command | str | Normalized command name. | required |
platform | str | Target platform (linux, osx, windows, etc.). | required |
language | str | Language code (en, es, fr, etc.). | required |
Returns:
Type | Description |
---|---|
Path | Full path to the cached page file. |
Source code in tux/wrappers/tldr.py
@staticmethod
def get_cache_file_path(command: str, platform: str, language: str) -> Path:
"""
Generate the file system path for a cached TLDR page.
Parameters
----------
command : str
Normalized command name.
platform : str
Target platform (linux, osx, windows, etc.).
language : str
Language code (en, es, fr, etc.).
Returns
-------
Path
Full path to the cached page file.
"""
pages_dir = f"pages{f'.{language}' if language != 'en' else ''}"
return CACHE_DIR / pages_dir / platform / f"{command}.md"
have_recent_cache(command: str, platform: str, language: str) -> bool
staticmethod
¶
Check if a recent cached version of a page exists.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
command | str | Command name to check. | required |
platform | str | Platform to check. | required |
language | str | Language to check. | required |
Returns:
Type | Description |
---|---|
bool | True if cached file exists and is within MAX_CACHE_AGE_HOURS. |
Source code in tux/wrappers/tldr.py
@staticmethod
def have_recent_cache(command: str, platform: str, language: str) -> bool:
"""
Check if a recent cached version of a page exists.
Parameters
----------
command : str
Command name to check.
platform : str
Platform to check.
language : str
Language to check.
Returns
-------
bool
True if cached file exists and is within MAX_CACHE_AGE_HOURS.
"""
try:
cache_file_path = TldrClient.get_cache_file_path(command, platform, language)
if not cache_file_path.exists():
return False
last_modified = cache_file_path.stat().st_mtime
hours_passed = (time.time() - last_modified) / 3600
except OSError:
return False
else:
return hours_passed <= MAX_CACHE_AGE_HOURS
load_page_from_cache(command: str, platform: str, language: str) -> str | None
staticmethod
¶
Load a TLDR page from local cache.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
command | str | Command name. | required |
platform | str | Platform name. | required |
language | str | Language code. | required |
Returns:
Type | Description |
---|---|
str | None | Page content if available, None if not found or on error. |
Source code in tux/wrappers/tldr.py
@staticmethod
def load_page_from_cache(command: str, platform: str, language: str) -> str | None:
"""
Load a TLDR page from local cache.
Parameters
----------
command : str
Command name.
platform : str
Platform name.
language : str
Language code.
Returns
-------
str | None
Page content if available, None if not found or on error.
"""
with contextlib.suppress(OSError):
cache_path = TldrClient.get_cache_file_path(command, platform, language)
if cache_path.exists():
return cache_path.read_text(encoding="utf-8")
return None
store_page_to_cache(page: str, command: str, platform: str, language: str) -> None
staticmethod
¶
Store a TLDR page to local cache.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
page | str | Page content to store. | required |
command | str | Command name. | required |
platform | str | Platform name. | required |
language | str | Language code. | required |
Source code in tux/wrappers/tldr.py
@staticmethod
def store_page_to_cache(page: str, command: str, platform: str, language: str) -> None:
"""
Store a TLDR page to local cache.
Parameters
----------
page : str
Page content to store.
command : str
Command name.
platform : str
Platform name.
language : str
Language code.
"""
with contextlib.suppress(OSError):
cache_file_path = TldrClient.get_cache_file_path(command, platform, language)
cache_file_path.parent.mkdir(parents=True, exist_ok=True)
cache_file_path.write_text(page, encoding="utf-8")
detect_platform() -> str
staticmethod
¶
Detect the default platform for Discord bot context.
Returns:
Type | Description |
---|---|
str | Platform identifier, defaults to 'linux' for container environments. |
get_language_priority(user_language: str | None = None) -> list[str]
staticmethod
¶
Get prioritized list of languages for Discord bot context.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
user_language | str | None | User-specified language preference. | None |
Returns:
Type | Description |
---|---|
list[str] | Ordered list of languages to try, always ending with 'en'. |
Source code in tux/wrappers/tldr.py
@staticmethod
def get_language_priority(user_language: str | None = None) -> list[str]:
"""
Get prioritized list of languages for Discord bot context.
Parameters
----------
user_language : str | None
User-specified language preference.
Returns
-------
list[str]
Ordered list of languages to try, always ending with 'en'.
"""
languages: list[str] = []
if user_language:
languages.append(user_language)
if "en" not in languages:
languages.append("en")
return languages
get_platform_priority(user_platform_input: str | None = None) -> list[str]
staticmethod
¶
Determine platform search order based on user input and TLDR spec.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
user_platform_input | str | None | User-specified platform preference. | None |
Returns:
Type | Description |
---|---|
list[str] | Ordered list of platforms to search, following TLDR specification. |
Notes
Implementation follows TLDR spec v2.3: - If user specifies "common", only return "common" - Otherwise: [user_platform, detected_platform, common, all_other_platforms]
Source code in tux/wrappers/tldr.py
@staticmethod
def get_platform_priority(user_platform_input: str | None = None) -> list[str]:
"""
Determine platform search order based on user input and TLDR spec.
Parameters
----------
user_platform_input : str | None
User-specified platform preference.
Returns
-------
list[str]
Ordered list of platforms to search, following TLDR specification.
Notes
-----
Implementation follows TLDR spec v2.3:
- If user specifies "common", only return "common"
- Otherwise: [user_platform, detected_platform, common, all_other_platforms]
"""
platforms_to_try: list[str] = []
# Handle explicit "common" request per TLDR spec
if user_platform_input == "common":
return ["common"]
# Add user-specified platform first
if user_platform_input and user_platform_input in SUPPORTED_PLATFORMS:
platforms_to_try.append(user_platform_input)
# Handle macos alias
if user_platform_input == "macos" and "osx" not in platforms_to_try:
platforms_to_try.append("osx")
# Add detected platform if different
detected_os = TldrClient.detect_platform()
if detected_os not in platforms_to_try:
platforms_to_try.append(detected_os)
# Add common as fallback
if "common" not in platforms_to_try:
platforms_to_try.append("common")
# Add all other platforms as final fallback per TLDR spec
for platform in SUPPORTED_PLATFORMS:
if platform not in platforms_to_try:
platforms_to_try.append(platform)
return platforms_to_try
fetch_tldr_page(command: str, languages: list[str], platform_preference: str | None = None) -> tuple[str, str] | None
staticmethod
¶
Fetch a TLDR page with platform priority and language fallback.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
command | str | Normalized command name to fetch. | required |
languages | list[str] | Ordered list of languages to try. | required |
platform_preference | str | None | User's platform preference. | None |
Returns:
Type | Description |
---|---|
tuple[str, str] | None | Tuple of (page_content, found_platform) if successful, None if not found. |
Notes
Follows TLDR spec priority: platform takes precedence over language. Tries cache first, then remote fetch with automatic caching.
Source code in tux/wrappers/tldr.py
@staticmethod
def fetch_tldr_page(
command: str,
languages: list[str],
platform_preference: str | None = None,
) -> tuple[str, str] | None:
"""
Fetch a TLDR page with platform priority and language fallback.
Parameters
----------
command : str
Normalized command name to fetch.
languages : list[str]
Ordered list of languages to try.
platform_preference : str | None
User's platform preference.
Returns
-------
tuple[str, str] | None
Tuple of (page_content, found_platform) if successful, None if not found.
Notes
-----
Follows TLDR spec priority: platform takes precedence over language.
Tries cache first, then remote fetch with automatic caching.
"""
platforms_to_try = TldrClient.get_platform_priority(platform_preference)
for language in languages:
for platform in platforms_to_try:
# Check cache first
if TldrClient.have_recent_cache(command, platform, language) and (
cache_content := TldrClient.load_page_from_cache(command, platform, language)
):
return (cache_content, platform)
# Fetch from remote
suffix = f".{language}" if language != "en" else ""
url = f"{PAGES_SOURCE_URL}{suffix}/{platform}/{command}.md"
try:
req = Request(url, headers={"User-Agent": "tldr-python-client"})
with urlopen(req, timeout=REQUEST_TIMEOUT_SECONDS) as resp:
page_content = resp.read().decode("utf-8")
TldrClient.store_page_to_cache(page_content, command, platform, language)
return (page_content, platform)
except (HTTPError, URLError):
continue # Try next platform/language combination
return None
list_tldr_commands(language: str = 'en', platform_filter: str | None = 'linux') -> list[str]
staticmethod
¶
List available TLDR commands for a given language and platform filter.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
language | str | Language code to search. | 'en' |
platform_filter | str | None | Platform to filter by. If None, searches linux + common platforms. | 'linux' |
Returns:
Type | Description |
---|---|
list[str] | Sorted list of available command names. |
Source code in tux/wrappers/tldr.py
@staticmethod
def list_tldr_commands(language: str = "en", platform_filter: str | None = "linux") -> list[str]:
"""
List available TLDR commands for a given language and platform filter.
Parameters
----------
language : str
Language code to search.
platform_filter : str | None
Platform to filter by. If None, searches linux + common platforms.
Returns
-------
list[str]
Sorted list of available command names.
"""
commands_set: set[str] = set()
normalized_lang_for_dir = "en" if language.startswith("en") else language
pages_dir_name = f"pages.{normalized_lang_for_dir}" if normalized_lang_for_dir != "en" else "pages"
# Handle platform filtering logic
if platform_filter is None:
# When no filter specified, search linux + common
platforms_to_scan = ["linux", "common"]
else:
# Use the specified platform
platforms_to_scan = [platform_filter]
# Always include common unless it was explicitly requested
if platform_filter != "common":
platforms_to_scan.append("common")
# Remove duplicates while keeping original order
unique_platforms_to_scan: list[str] = []
seen_platforms: set[str] = set()
for platform in platforms_to_scan:
if platform not in seen_platforms:
unique_platforms_to_scan.append(platform)
seen_platforms.add(platform)
for platform in unique_platforms_to_scan:
path: Path = CACHE_DIR / pages_dir_name / platform
try:
# Skip if path doesn't exist
if not path.exists() or not path.is_dir():
continue
# Collect all .md files
found_in_platform: set[str] = {file.stem for file in path.iterdir() if file.suffix == ".md"}
commands_set.update(found_in_platform)
except OSError:
continue
return sorted(commands_set)
parse_placeholders(line: str, show_short: bool = False, show_long: bool = True, show_both: bool = False, highlight: bool = True) -> str
staticmethod
¶
Parse and format placeholder text in TLDR pages.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
line | str | Line containing TLDR placeholder syntax. | required |
show_short | bool | Show only short options for placeholders. | False |
show_long | bool | Show only long options for placeholders. | True |
show_both | bool | Show both short and long options. | False |
highlight | bool | Whether to apply highlighting markup. | True |
Returns:
Type | Description |
---|---|
str | Processed line with placeholders resolved. |
Source code in tux/wrappers/tldr.py
@staticmethod
def parse_placeholders(
line: str,
show_short: bool = False,
show_long: bool = True,
show_both: bool = False,
highlight: bool = True,
) -> str:
"""
Parse and format placeholder text in TLDR pages.
Parameters
----------
line : str
Line containing TLDR placeholder syntax.
show_short : bool
Show only short options for placeholders.
show_long : bool
Show only long options for placeholders.
show_both : bool
Show both short and long options.
highlight : bool
Whether to apply highlighting markup.
Returns
-------
str
Processed line with placeholders resolved.
"""
line = line.replace(r"\{\{", "__TEMP_ESCAPED_OPEN__")
line = line.replace(r"\}\}", "__TEMP_ESCAPED_CLOSE__")
def repl(match: re.Match[str]) -> str:
content = match.group(1)
if content.startswith("[") and content.endswith("]") and "|" in content:
short, long = content[1:-1].split("|", 1)
if show_both:
chosen = f"{short}|{long}"
elif show_short:
chosen = short
else:
chosen = long
else:
chosen = content
# Only underline if not a literal option (doesn't start with '-')
if highlight and not chosen.lstrip().startswith("-"):
return f"__{chosen}__"
return chosen
line = re.sub(r"\{\{(.*?)\}\}", repl, line)
line = line.replace("__TEMP_ESCAPED_OPEN__", "{{")
return line.replace("__TEMP_ESCAPED_CLOSE__", "}}")
_process_description_lines(lines: list[str], i: int, show_short: bool, show_long: bool, show_both: bool) -> tuple[list[str], int]
staticmethod
¶
Process consecutive description lines starting with '>'.
Source code in tux/wrappers/tldr.py
@staticmethod
def _process_description_lines(
lines: list[str],
i: int,
show_short: bool,
show_long: bool,
show_both: bool,
) -> tuple[list[str], int]:
"""Process consecutive description lines starting with '>'."""
description_lines: list[str] = []
while i < len(lines):
line = lines[i].rstrip()
if not line.startswith(">"):
break
parsed_line = TldrClient.parse_placeholders(
line[1:].strip(),
show_short,
show_long,
show_both,
highlight=True,
)
description_lines.append(parsed_line)
i += 1
return description_lines, i
_process_command_examples(lines: list[str], i: int, show_short: bool, show_long: bool, show_both: bool) -> tuple[list[str], int]
staticmethod
¶
Process command examples and descriptions.
Source code in tux/wrappers/tldr.py
@staticmethod
def _process_command_examples(
lines: list[str],
i: int,
show_short: bool,
show_long: bool,
show_both: bool,
) -> tuple[list[str], int]:
"""Process command examples and descriptions."""
formatted: list[str] = []
last_was_command = False
first_description_found = False
while i < len(lines):
current_line = lines[i].rstrip()
if not current_line:
i += 1
continue
if current_line.startswith("- "):
# Add spacing before first description to separate from initial description
if not first_description_found:
formatted.append("")
first_description_found = True
# If last item was a command, add spacing before new description
elif last_was_command:
formatted.append("")
# Command descriptions become regular text (no block quotes)
current_line = TldrClient.parse_placeholders(
current_line,
show_short,
show_long,
show_both,
highlight=True,
)
description_content = current_line[2:] # Remove "- " prefix
formatted.append(description_content)
last_was_command = False
elif current_line.startswith("`") and current_line.endswith("`"):
# Command examples become bullet points
current_line = TldrClient.parse_placeholders(
current_line,
show_short,
show_long,
show_both,
highlight=False,
)
code_content = current_line[1:-1] # Remove backticks
formatted.append(f"- `{code_content}`")
last_was_command = True
else:
current_line = TldrClient.parse_placeholders(
current_line,
show_short,
show_long,
show_both,
highlight=True,
)
formatted.append(current_line)
last_was_command = False
i += 1
return formatted, i
format_tldr_for_discord(md: str, show_short: bool = False, show_long: bool = True, show_both: bool = False) -> str
staticmethod
¶
Format a TLDR markdown page for Discord output.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
md | str | Raw TLDR markdown content. | required |
show_short | bool | Show only short options for placeholders. | False |
show_long | bool | Show only long options for placeholders. | True |
show_both | bool | Show both short and long options. | False |
Returns:
Type | Description |
---|---|
str | Formatted content suitable for Discord display. |
Source code in tux/wrappers/tldr.py
@staticmethod
def format_tldr_for_discord(
md: str,
show_short: bool = False,
show_long: bool = True,
show_both: bool = False,
) -> str:
"""
Format a TLDR markdown page for Discord output.
Parameters
----------
md : str
Raw TLDR markdown content.
show_short : bool
Show only short options for placeholders.
show_long : bool
Show only long options for placeholders.
show_both : bool
Show both short and long options.
Returns
-------
str
Formatted content suitable for Discord display.
"""
lines = md.splitlines()
formatted: list[str] = []
i = 0
n = len(lines)
# Find and skip the title
while i < n:
line = lines[i].rstrip()
if line.startswith("# "):
i += 1
break
i += 1
# Process description lines
description_lines, i = TldrClient._process_description_lines(lines, i, show_short, show_long, show_both)
if description_lines:
formatted.append("> " + "\n> ".join(description_lines))
# Skip any standalone command name line after the description
if i < n and lines[i].strip():
# Skip potential command name line
i += 1
# Process command examples and descriptions
command_formatted, _ = TldrClient._process_command_examples(lines, i, show_short, show_long, show_both)
formatted.extend(command_formatted)
return "\n".join(formatted)
not_found_message(command: str) -> str
staticmethod
¶
Generate a message for when a page is not found.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
command | str | Command that was not found. | required |
Returns:
Type | Description |
---|---|
str | Formatted not found message with GitHub link. |
Source code in tux/wrappers/tldr.py
@staticmethod
def not_found_message(command: str) -> str:
"""
Generate a message for when a page is not found.
Parameters
----------
command : str
Command that was not found.
Returns
-------
str
Formatted not found message with GitHub link.
"""
url = f"https://github.com/tldr-pages/tldr/issues/new?title=page%20request:{command}"
return f"No TLDR page found for `{command}`.\n[Request it on GitHub]({url})"
update_tldr_cache(language: str = 'en') -> str
staticmethod
¶
Update the TLDR cache for a specific language.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
language | str | Language code to update cache for. | 'en' |
Returns:
Type | Description |
---|---|
str | Status message indicating success or failure. |
Notes
Downloads from GitHub releases following TLDR spec v2.3. Replaces existing cache completely to ensure consistency.
Source code in tux/wrappers/tldr.py
@staticmethod
def update_tldr_cache(language: str = "en") -> str:
"""
Update the TLDR cache for a specific language.
Parameters
----------
language : str
Language code to update cache for.
Returns
-------
str
Status message indicating success or failure.
Notes
-----
Downloads from GitHub releases following TLDR spec v2.3.
Replaces existing cache completely to ensure consistency.
"""
suffix = "" if language.startswith("en") else f".{language}"
pages_dir_name = "pages" if language.startswith("en") else f"pages.{language}"
url = ARCHIVE_URL_TEMPLATE.format(suffix=suffix)
try:
req = Request(url, headers={"User-Agent": "tldr-python-client", "Accept": "application/zip"})
with urlopen(req, timeout=30) as resp:
content = resp.read()
# Validate content
if content.strip().lower().startswith((b"<!doctype html", b"<html>")):
return f"Failed to update cache for '{language}': Invalid content received"
target_path = CACHE_DIR / pages_dir_name
# More robust cache directory cleanup
if target_path.exists():
try:
shutil.rmtree(target_path)
except OSError:
# If rmtree fails, try to remove contents manually
for item in target_path.rglob("*"):
try:
if item.is_file():
item.unlink()
elif item.is_dir():
item.rmdir()
except OSError:
continue
# Try final cleanup
with contextlib.suppress(OSError):
target_path.rmdir()
target_path.mkdir(parents=True, exist_ok=True)
# Extract archive
with zipfile.ZipFile(BytesIO(content)) as archive:
archive.extractall(target_path)
return f"Cache updated for language `{language}` from {url}"
except HTTPError as e:
if e.code == 404:
return f"Failed to update cache for '{language}': Archive not found (404)"
return f"Failed to update cache for '{language}': {e}"
except zipfile.BadZipFile:
return f"Failed to update cache for '{language}': Invalid zip file"
except Exception as e:
return f"Failed to update cache for '{language}': {e}"
cache_needs_update(language: str = 'en') -> bool
staticmethod
¶
Check if the cache needs updating based on age.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
language | str | Language to check cache for. | 'en' |
Returns:
Type | Description |
---|---|
bool | True if cache is missing or older than MAX_CACHE_AGE_HOURS. |
Source code in tux/wrappers/tldr.py
@staticmethod
def cache_needs_update(language: str = "en") -> bool:
"""
Check if the cache needs updating based on age.
Parameters
----------
language : str
Language to check cache for.
Returns
-------
bool
True if cache is missing or older than MAX_CACHE_AGE_HOURS.
"""
pages_dir_name = "pages" if language.startswith("en") else f"pages.{language}"
cache_dir = CACHE_DIR / pages_dir_name
if not cache_dir.exists():
return True
try:
last_modified = cache_dir.stat().st_mtime
hours_passed = (time.time() - last_modified) / 3600
except (FileNotFoundError, PermissionError):
return True
else:
return hours_passed > MAX_CACHE_AGE_HOURS
split_long_text(text: str, max_len: int = 4000) -> list[str]
staticmethod
¶
Split long text into pages for Discord embeds.
Parameters:
Name | Type | Description | Default |
---|---|---|---|
text | str | Text to split. | required |
max_len | int | Maximum length per page. | 4000 |
Returns:
Type | Description |
---|---|
list[str] | List of text chunks within max_len limits. |
Source code in tux/wrappers/tldr.py
@staticmethod
def split_long_text(text: str, max_len: int = 4000) -> list[str]:
"""
Split long text into pages for Discord embeds.
Parameters
----------
text : str
Text to split.
max_len : int
Maximum length per page.
Returns
-------
list[str]
List of text chunks within max_len limits.
"""
lines = text.splitlines(keepends=True)
pages: list[str] = []
current_text_chunk = ""
for line_content in lines:
if len(current_text_chunk) + len(line_content) > max_len:
pages.append(current_text_chunk)
current_text_chunk = ""
current_text_chunk += line_content
if current_text_chunk:
pages.append(current_text_chunk)
return pages